Official Version V0.1 Release
This commit is contained in:
parent
2375162f30
commit
97246370c5
122
dmrlink.py
122
dmrlink.py
|
@ -24,6 +24,15 @@ 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
|
||||||
|
|
||||||
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
|
__copyright__ = 'Copyright (c) 2013 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
__credits__ = 'Adam Fast, KC0YLK, Dave K, and he who wishes not to be named'
|
||||||
|
__license__ = 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
||||||
|
__version__ = '0.1'
|
||||||
|
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||||
|
__email__ = 'n0mjs@me.com'
|
||||||
|
__status__ = 'Production'
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# PARSE THE CONFIG FILE AND BUILD STRUCTURE
|
# PARSE THE CONFIG FILE AND BUILD STRUCTURE
|
||||||
#************************************************
|
#************************************************
|
||||||
|
@ -40,9 +49,11 @@ except:
|
||||||
try:
|
try:
|
||||||
for section in config.sections():
|
for section in config.sections():
|
||||||
if section == 'GLOBAL':
|
if section == 'GLOBAL':
|
||||||
|
# Process GLOBAL items in the configuration
|
||||||
PATH = config.get(section, 'PATH')
|
PATH = config.get(section, 'PATH')
|
||||||
|
|
||||||
elif section == 'REPORTS':
|
elif section == 'REPORTS':
|
||||||
|
# Process REPORTS items in the configuration
|
||||||
REPORTS = {
|
REPORTS = {
|
||||||
'REPORT_PEERS': config.getboolean(section, 'REPORT_PEERS'),
|
'REPORT_PEERS': config.getboolean(section, 'REPORT_PEERS'),
|
||||||
'PEER_REPORT_INC_MODE': config.getboolean(section, 'PEER_REPORT_INC_MODE'),
|
'PEER_REPORT_INC_MODE': config.getboolean(section, 'PEER_REPORT_INC_MODE'),
|
||||||
|
@ -50,32 +61,49 @@ try:
|
||||||
}
|
}
|
||||||
|
|
||||||
elif section == 'LOGGER':
|
elif section == 'LOGGER':
|
||||||
|
# Process LOGGER items in the configuration
|
||||||
LOGGER = {
|
LOGGER = {
|
||||||
'LOG_FILE': config.get(section, 'LOG_FILE'),
|
'LOG_FILE': config.get(section, 'LOG_FILE'),
|
||||||
'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
|
'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
|
||||||
'LOG_LEVEL': config.get(section, 'LOG_LEVEL')
|
'LOG_LEVEL': config.get(section, 'LOG_LEVEL')
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
# 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': {}}})
|
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({
|
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': '',
|
'MODE': '',
|
||||||
'PEER_OPER': True,
|
|
||||||
'PEER_MODE': 'DIGITAL',
|
# These items are used to create the multi-byte FLAGS field
|
||||||
'FLAGS': '',
|
|
||||||
'MAX_MISSED': 20,
|
|
||||||
'NUM_PEERS': 0,
|
|
||||||
'STATUS': {
|
|
||||||
'ACTIVE': False
|
|
||||||
},
|
|
||||||
'ENABLED': config.getboolean(section, 'ENABLED'),
|
|
||||||
'TS1_LINK': config.getboolean(section, 'TS1_LINK'),
|
|
||||||
'TS2_LINK': config.getboolean(section, 'TS2_LINK'),
|
|
||||||
'AUTH_ENABLED': config.getboolean(section, 'AUTH_ENABLED'),
|
'AUTH_ENABLED': config.getboolean(section, 'AUTH_ENABLED'),
|
||||||
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
'CSBK_CALL': config.getboolean(section, 'CSBK_CALL'),
|
||||||
'PORT': config.getint(section, 'PORT'),
|
'RCM': config.getboolean(section, 'RCM'),
|
||||||
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
|
'CON_APP': config.getboolean(section, 'CON_APP'),
|
||||||
'AUTH_KEY': (config.get(section, 'AUTH_KEY').rjust(40,'0')).decode('hex'),
|
'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'),
|
||||||
|
'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({
|
NETWORK[section]['MASTER'].update({
|
||||||
'RADIO_ID': '\x00\x00\x00\x00',
|
'RADIO_ID': '\x00\x00\x00\x00',
|
||||||
'MODE': '\x00',
|
'MODE': '\x00',
|
||||||
|
@ -92,22 +120,51 @@ try:
|
||||||
'IP': config.get(section, 'MASTER_IP'),
|
'IP': config.get(section, 'MASTER_IP'),
|
||||||
'PORT': config.getint(section, 'MASTER_PORT')
|
'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 NETWORK[section]['LOCAL']['PEER_OPER']:
|
||||||
|
MODE_BYTE |= 1 << 6
|
||||||
|
if NETWORK[section]['LOCAL']['IPSC_MODE'] == 'ANALOG':
|
||||||
|
MODE_BYTE |= 1 << 4
|
||||||
|
elif NETWORK[section]['LOCAL']['IPSC_MODE'] == 'DIGITAL':
|
||||||
|
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
|
||||||
|
if NETWORK[section]['LOCAL']['CSBK_CALL']:
|
||||||
|
FLAG_1 |= 1 << 7
|
||||||
|
if NETWORK[section]['LOCAL']['RCM']:
|
||||||
|
FLAG_1 |= 1 << 6
|
||||||
|
if NETWORK[section]['LOCAL']['CON_APP']:
|
||||||
|
FLAG_1 |= 1 << 5
|
||||||
|
if NETWORK[section]['LOCAL']['XNL_CALL']:
|
||||||
|
FLAG_2 |= 1 << 7
|
||||||
|
if NETWORK[section]['LOCAL']['XNL_CALL'] and NETWORK[section]['LOCAL']['XNL_MASTER']:
|
||||||
|
FLAG_2 |= 1 << 6
|
||||||
|
elif NETWORK[section]['LOCAL']['XNL_CALL'] and not NETWORK[section]['LOCAL']['XNL_MASTER']:
|
||||||
|
FLAG_2 |= 1 << 5
|
||||||
if NETWORK[section]['LOCAL']['AUTH_ENABLED']:
|
if NETWORK[section]['LOCAL']['AUTH_ENABLED']:
|
||||||
#0x60 - 3rd Party App & Repeater Monitoring, 0x1C - Voice and Data calls only, 0xDC - Voice, Data and XCMP/XNL
|
FLAG_2 |= 1 << 4
|
||||||
NETWORK[section]['LOCAL']['FLAGS'] = '\x00\x00\x60\x1C'
|
if NETWORK[section]['LOCAL']['DATA_CALL']:
|
||||||
#NETWORK[section]['LOCAL']['FLAGS'] = '\x00\x00\x60\xDC'
|
FLAG_2 |= 1 << 3
|
||||||
else:
|
if NETWORK[section]['LOCAL']['VOICE_CALL']:
|
||||||
NETWORK[section]['LOCAL']['FLAGS'] = '\x00\x00\x60\x0C'
|
FLAG_2 |= 1 << 2
|
||||||
|
if NETWORK[section]['LOCAL']['MASTER_PEER']:
|
||||||
if not NETWORK[section]['LOCAL']['TS1_LINK'] and not NETWORK[section]['LOCAL']['TS2_LINK']:
|
FLAG_2 |= 1 << 0
|
||||||
NETWORK[section]['LOCAL']['MODE'] = '\x65'
|
NETWORK[section]['LOCAL']['FLAGS'] = '\x00\x00'+chr(FLAG_1)+chr(FLAG_2)
|
||||||
elif NETWORK[section]['LOCAL']['TS1_LINK'] and not NETWORK[section]['LOCAL']['TS2_LINK']:
|
|
||||||
NETWORK[section]['LOCAL']['MODE'] = '\x66'
|
|
||||||
elif not NETWORK[section]['LOCAL']['TS1_LINK'] and NETWORK[section]['LOCAL']['TS2_LINK']:
|
|
||||||
NETWORK[section]['LOCAL']['MODE'] = '\x69'
|
|
||||||
else:
|
|
||||||
NETWORK[section]['LOCAL']['MODE'] = '\x6A'
|
|
||||||
except:
|
except:
|
||||||
sys.exit('Could not parse configuration file, exiting...')
|
sys.exit('Could not parse configuration file, exiting...')
|
||||||
|
|
||||||
|
@ -395,7 +452,7 @@ def process_peer_list(_data, _network):
|
||||||
for peerid in NETWORK[_network]['PEERS'].keys():
|
for peerid in NETWORK[_network]['PEERS'].keys():
|
||||||
if peerid not in _temp_peers:
|
if peerid not in _temp_peers:
|
||||||
de_register_peer(_network, peerid)
|
de_register_peer(_network, peerid)
|
||||||
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, peerid)
|
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, h(peerid))
|
||||||
|
|
||||||
|
|
||||||
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
|
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
|
||||||
|
@ -479,7 +536,6 @@ class IPSC(DatagramProtocol):
|
||||||
self._config = NETWORK[self._network]
|
self._config = NETWORK[self._network]
|
||||||
#
|
#
|
||||||
self._local = self._config['LOCAL']
|
self._local = self._config['LOCAL']
|
||||||
self._local_stat = self._local['STATUS']
|
|
||||||
self._local_id = self._local['RADIO_ID']
|
self._local_id = self._local['RADIO_ID']
|
||||||
#
|
#
|
||||||
self._master = self._config['MASTER']
|
self._master = self._config['MASTER']
|
||||||
|
@ -946,4 +1002,4 @@ if __name__ == '__main__':
|
||||||
else:
|
else:
|
||||||
networks[ipsc_network] = UnauthIPSC(ipsc_network)
|
networks[ipsc_network] = UnauthIPSC(ipsc_network)
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network])
|
||||||
reactor.run()
|
reactor.run()
|
|
@ -41,6 +41,8 @@ LOG_LEVEL: CRITICAL
|
||||||
|
|
||||||
|
|
||||||
# CONFIGURATION FOR IPSC NETWORKS
|
# CONFIGURATION FOR IPSC NETWORKS
|
||||||
|
# Please read these closely - catastrophic results could result by setting
|
||||||
|
# certain flags for things DMRlink cannot do.
|
||||||
#
|
#
|
||||||
# [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"...)
|
||||||
|
@ -49,7 +51,25 @@ LOG_LEVEL: CRITICAL
|
||||||
# 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
|
||||||
# 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
|
||||||
# IPSC network, must be unique!!!
|
# IPSC network, must be unique!!!
|
||||||
# ALIVE_TIMER: How many missed keep-alives before we remove a peer
|
# ALIVE_TIMER: Seconds between keep-alive transmissions
|
||||||
|
# MAX_MISSED: How many missed keep-alives before we remove a peer
|
||||||
|
# PEER_OPER: This signals the master and peers whether or not we are
|
||||||
|
# operational. True is the only thing that makes sense.
|
||||||
|
# IPSC_MODE: May be 'DIGITAL', 'ANALOG', or 'NONE'. Digital is really the
|
||||||
|
# only thing that makes sense.
|
||||||
|
# TSx_LINK: Is this time slot linked?
|
||||||
|
# CSBK_CALL: Should be False, we cannot process these, but may be useful
|
||||||
|
# for debugging.
|
||||||
|
# RCM: Repeater Call Monitoring - don't unable unless you plan to
|
||||||
|
# actually use it, this craetes extra network traffic.
|
||||||
|
# CON_APP: Third Party Console App - exactly what DMRlink is, should
|
||||||
|
# be set to True.
|
||||||
|
# XNL_CALL: Can cause problems if not set to False, DMRlink does not
|
||||||
|
# process XCMP/XNL calls.
|
||||||
|
# XNL_MASTER: Obviously, should also be False, see XNL_CALL.
|
||||||
|
# DATA_CALL: Process data calls. True if you want to process data calls
|
||||||
|
# VOICE_CALL: Process voice calls. True if you want to process voice calls
|
||||||
|
# MASTER_PEER: Must be False, we cannot yet act as a master peer.
|
||||||
# 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
|
# MASTER_IP: IP address of the IPSC master
|
||||||
|
@ -69,14 +89,25 @@ AUTH_KEY: 1
|
||||||
MASTER_IP: 1.2.3.4
|
MASTER_IP: 1.2.3.4
|
||||||
MASTER_PORT: 50000
|
MASTER_PORT: 50000
|
||||||
|
|
||||||
[IPSC2]
|
[IPSC1]
|
||||||
ENABLED: True
|
ENABLED: True
|
||||||
RADIO_ID: 2
|
RADIO_ID: 12345
|
||||||
PORT: 50001
|
PORT: 50000
|
||||||
ALIVE_TIMER: 5
|
ALIVE_TIMER: 5
|
||||||
|
MAX_MISSED: 20
|
||||||
|
PEER_OPER = True
|
||||||
|
IPSC_MODE = DIGITAL
|
||||||
TS1_LINK: True
|
TS1_LINK: True
|
||||||
TS2_LINK: True
|
TS2_LINK: True
|
||||||
AUTH_ENABLED: True
|
CSBK_CALL = False
|
||||||
AUTH_KEY: 2
|
RCM = True
|
||||||
MASTER_IP: 5.6.7.8
|
CON_APP = True
|
||||||
MASTER_PORT: 50000
|
XNL_CALL = False
|
||||||
|
XNL_MASTER = False
|
||||||
|
DATA_CALL = True
|
||||||
|
VOICE_CALL = True
|
||||||
|
MASTER_PEER = False
|
||||||
|
AUTH_ENABLED = True
|
||||||
|
AUTH_KEY: 1A2B3C
|
||||||
|
MASTER_IP: 1.2.3.4
|
||||||
|
MASTER_PORT: 50000
|
||||||
|
|
Loading…
Reference in New Issue