Merge branch 'modularization'

This commit is contained in:
Cort Buffington 2016-12-18 21:59:01 -06:00
commit 8dbba4b42d
16 changed files with 53670 additions and 29119 deletions

View File

@ -12,8 +12,8 @@ gateway = 127.0.0.1 # IP address of DMRGateway app
toGatewayPort = 31000 # Port DMRGateway is listening on for AMBE frames to decode toGatewayPort = 31000 # Port DMRGateway is listening on for AMBE frames to decode
remoteControlPort = 31002 # Port that ambe_audio is listening on for remote control commands remoteControlPort = 31002 # Port that ambe_audio is listening on for remote control commands
fromGatewayPort = 31003 # Port to listen on for AMBE frames to transmit to all peers fromGatewayPort = 31003 # Port to listen on for AMBE frames to transmit to all peers
gatewayDmrId = 0 # id to use when transmitting from the gateway gatewayDmrId = 1312000 # id to use when transmitting from the gateway
tgFilter = 9 # A list of TG IDs to monitor. All TGs will be passed to DMRGateway tgFilter = 9 # A list of TG IDs to monitor. All TGs will be passed to DMRGateway
txTg = 9 # TG to use for all frames received from DMRGateway -> IPSC txTg = 9 # TG to use for all frames received from DMRGateway -> IPSC
txTs = 2 # Slot to use for frames received from DMRGateway -> IPSC txTs = 2 # Slot to use for frames received from DMRGateway -> IPSC
# #

View File

@ -28,7 +28,10 @@ from bitstring import BitArray
import sys, socket, ConfigParser, thread, traceback import sys, socket, ConfigParser, thread, traceback
import cPickle as pickle import cPickle as pickle
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3, hex_str_4, get_info, talkgroup_ids, peer_ids, PATH, get_subscriber_info, reread_subscribers
from dmrlink import IPSC, systems
from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias
from time import time, sleep, clock, localtime, strftime from time import time, sleep, clock, localtime, strftime
import csv import csv
import struct import struct
@ -90,8 +93,8 @@ class ambeIPSC(IPSC):
#_d = None #_d = None
###### DEBUGDEBUGDEBUG ###### DEBUGDEBUGDEBUG
def __init__(self, *args, **kwargs): def __init__(self, _name, _config, _logger):
IPSC.__init__(self, *args, **kwargs) IPSC.__init__(self, _name, _config, _logger)
self.CALL_DATA = [] self.CALL_DATA = []
# #
@ -99,7 +102,7 @@ class ambeIPSC(IPSC):
# #
self._currentTG = self._no_tg self._currentTG = self._no_tg
self._currentNetwork = str(args[0]) self._currentNetwork = str(_name)
self.readConfigFile(self._configFile, None, self._currentNetwork) self.readConfigFile(self._configFile, None, self._currentNetwork)
logger.info('DMRLink ambe server') logger.info('DMRLink ambe server')
@ -121,7 +124,7 @@ class ambeIPSC(IPSC):
try: try:
thread.start_new_thread( self.remote_control, (self._remote_control_port, ) ) # Listen for remote control commands thread.start_new_thread( self.remote_control, (self._remote_control_port, ) ) # Listen for remote control commands
thread.start_new_thread( self.launchUDP, (args[0], ) ) # Package AMBE into IPSC frames and send to all peers thread.start_new_thread( self.launchUDP, (_name, ) ) # Package AMBE into IPSC frames and send to all peers
except: except:
traceback.print_exc() traceback.print_exc()
logger.error( "Error: unable to start thread" ) logger.error( "Error: unable to start thread" )
@ -178,7 +181,7 @@ class ambeIPSC(IPSC):
traceback.print_exc() traceback.print_exc()
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...') sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
def rewriteFrame( self, _frame, _network, _newSlot, _newGroup, _newSouceID, _newPeerID ): def rewriteFrame( self, _frame, _newSlot, _newGroup, _newSouceID, _newPeerID ):
_peerid = _frame[1:5] # int32 peer who is sending us a packet _peerid = _frame[1:5] # int32 peer who is sending us a packet
_src_sub = _frame[6:9] # int32 Id of source _src_sub = _frame[6:9] # int32 Id of source
@ -266,7 +269,7 @@ class ambeIPSC(IPSC):
return _ambeAll.tobytes() # Return the 49 * 3 as an array of bytes return _ambeAll.tobytes() # Return the 49 * 3 as an array of bytes
# Set up the socket and run the method to gather the AMBE. Sending it to all peers # Set up the socket and run the method to gather the AMBE. Sending it to all peers
def launchUDP(self, _network): def launchUDP(self):
s = socket.socket() # Create a socket object s = socket.socket() # Create a socket object
s.bind(('', self._ambeRxPort)) # Bind to the port s.bind(('', self._ambeRxPort)) # Bind to the port
@ -274,16 +277,16 @@ class ambeIPSC(IPSC):
s.listen(5) # Now wait for client connection. s.listen(5) # Now wait for client connection.
_sock, addr = s.accept() # Establish connection with client. _sock, addr = s.accept() # Establish connection with client.
if int_id(self._tx_tg) > 0: # Test if we are allowed to transmit if int_id(self._tx_tg) > 0: # Test if we are allowed to transmit
self.playbackFromUDP(_sock, _network) self.playbackFromUDP(_sock, self._system)
else: else:
self.transmitDisabled(_sock, _network) #tg is zero, so just eat the network trafic self.transmitDisabled(_sock, self._system) #tg is zero, so just eat the network trafic
_sock.close() _sock.close()
# This represents a full transmission (HEAD, VOICE and TERM) # This represents a full transmission (HEAD, VOICE and TERM)
def playbackFromUDP(self, _sock, _network): def playbackFromUDP(self, _sock):
_delay = 0.055 # Yes, I know it should be 0.06, but there seems to be some latency, so this is a hack _delay = 0.055 # Yes, I know it should be 0.06, but there seems to be some latency, so this is a hack
_src_sub = hex_str_3(self._gateway_dmr_id) # DMR ID to sign this transmission with _src_sub = hex_str_3(self._gateway_dmr_id) # DMR ID to sign this transmission with
_src_peer = NETWORK[_network]['LOCAL']['RADIO_ID'] # Use this peers ID as the source repeater _src_peer = NETWORK[self._system]['LOCAL']['RADIO_ID'] # Use this peers ID as the source repeater
logger.info('Transmit from gateway to TG {}:'.format(int_id(self._tx_tg)) ) logger.info('Transmit from gateway to TG {}:'.format(int_id(self._tx_tg)) )
try: try:
@ -310,8 +313,8 @@ class ambeIPSC(IPSC):
self._seq = randint(0,32767) # A transmission uses a random number to begin its sequence (16 bit) self._seq = randint(0,32767) # A transmission uses a random number to begin its sequence (16 bit)
for i in range(0, 3): # Output the 3 HEAD frames to our peers for i in range(0, 3): # Output the 3 HEAD frames to our peers
self.rewriteFrame(_tempHead[i], _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer) self.rewriteFrame(_tempHead[i], self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i]) #self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
sleep(_delay) sleep(_delay)
i = 0 # Initialize the VOICE template index i = 0 # Initialize the VOICE template index
@ -321,21 +324,21 @@ class ambeIPSC(IPSC):
i = (i + 1) % 6 # Round robbin with the 6 VOICE templates i = (i + 1) % 6 # Round robbin with the 6 VOICE templates
_frame = _tempVoice[i][:33] + _ambe + _tempVoice[i][52:] # Insert the 3 49 bit AMBE frames _frame = _tempVoice[i][:33] + _ambe + _tempVoice[i][52:] # Insert the 3 49 bit AMBE frames
self.rewriteFrame(_frame, _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer) self.rewriteFrame(_frame, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame) #self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame)
sleep(_delay) # Since this comes from a file we have to add delay between IPSC frames sleep(_delay) # Since this comes from a file we have to add delay between IPSC frames
else: else:
_eof = True # There are no more AMBE frames, so terminate the loop _eof = True # There are no more AMBE frames, so terminate the loop
self.rewriteFrame(_tempTerm, _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer) self.rewriteFrame(_tempTerm, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm) #self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
except IOError: except IOError:
logger.error('Can not transmit to peers') logger.error('Can not transmit to peers')
logger.info('Transmit complete') logger.info('Transmit complete')
def transmitDisabled(self, _sock, _network): def transmitDisabled(self, _sock):
_eof = False _eof = False
logger.debug('Transmit disabled begin') logger.debug('Transmit disabled begin')
while(_eof == False): while(_eof == False):
@ -386,20 +389,19 @@ class ambeIPSC(IPSC):
#************************************************ #************************************************
# #
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
#self.dumpIPSCFrame(_data) #self.dumpIPSCFrame(_data)
# THIS FUNCTION IS NOT COMPLETE!!!! # THIS FUNCTION IS NOT COMPLETE!!!!
_payload_type = _data[30:31] _payload_type = _data[30:31]
# _ambe_frames = _data[33:52] # _ambe_frames = _data[33:52]
_ambe_frames = BitArray('0x'+h(_data[33:52])) _ambe_frames = BitArray('0x'+h(_data[33:52]))
_ambe_frame1 = _ambe_frames[0:49] _ambe_frame1 = _ambe_frames[0:49]
_ambe_frame2 = _ambe_frames[50:99] _ambe_frame2 = _ambe_frames[50:99]
_ambe_frame3 = _ambe_frames[100:149] _ambe_frame3 = _ambe_frames[100:149]
_tg_id = int_id(_dst_sub) _tg_id = int_id(_dst_sub)
_ts = 2 if _ts else 1
self._busy_slots[_ts] = time() self._busy_slots[_ts] = time()
@ -409,12 +411,12 @@ class ambeIPSC(IPSC):
# self._d.write(struct.pack("i", __iLen)) # self._d.write(struct.pack("i", __iLen))
# self._d.write(_data) # self._d.write(_data)
# else: # else:
# self.rewriteFrame(_data, _network, 1, 9) # self.rewriteFrame(_data, self._system, 1, 9)
###### DEBUGDEBUGDEBUG ###### DEBUGDEBUGDEBUG
if _tg_id in self._tg_filter: #All TGs if _tg_id in self._tg_filter: #All TGs
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids) _dst_sub = get_alias(_dst_sub, talkgroup_ids)
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']: if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
if self._currentTG == self._no_tg: if self._currentTG == self._no_tg:
_src_sub = get_subscriber_info(_src_sub) _src_sub = get_subscriber_info(_src_sub)
@ -459,7 +461,7 @@ class ambeIPSC(IPSC):
else: else:
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']: if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids) _dst_sub = get_alias(_dst_sub, talkgroup_ids)
logger.warning('Ignored Voice Transmission Start on TS {} and TG {}'.format(_ts, _dst_sub)) logger.warning('Ignored Voice Transmission Start on TS {} and TG {}'.format(_ts, _dst_sub))
def outputFrames(self, _ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3): def outputFrames(self, _ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3):
@ -479,7 +481,7 @@ class ambeIPSC(IPSC):
self._sock.sendto(_ambe_frame2.tobytes(), (self._gateway, self._gateway_port)) self._sock.sendto(_ambe_frame2.tobytes(), (self._gateway, self._gateway_port))
self._sock.sendto(_ambe_frame3.tobytes(), (self._gateway, self._gateway_port)) self._sock.sendto(_ambe_frame3.tobytes(), (self._gateway, self._gateway_port))
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
print('private voice') print('private voice')
# __iLen = len(_data) # __iLen = len(_data)
# self._d.write(struct.pack("i", __iLen)) # self._d.write(struct.pack("i", __iLen))
@ -528,7 +530,7 @@ class ambeIPSC(IPSC):
print('New gateway_dmr_id = ' + str(self._gateway_dmr_id)) print('New gateway_dmr_id = ' + str(self._gateway_dmr_id))
elif _cmd == 'gateway_peer_id': elif _cmd == 'gateway_peer_id':
peerID = int(_tmp.split('=')[1]) peerID = int(_tmp.split('=')[1])
NETWORK[_network]['LOCAL']['RADIO_ID'] = hex_str_3(peerID) self._config['LOCAL']['RADIO_ID'] = hex_str_3(peerID)
print('New peer_id = ' + str(peerID)) print('New peer_id = ' + str(peerID))
elif _cmd == 'restart': elif _cmd == 'restart':
reactor.callFromThread(reactor.stop) reactor.callFromThread(reactor.stop)
@ -540,9 +542,9 @@ class ambeIPSC(IPSC):
logger.info( 'New TGs={}'.format(self._tg_filter) ) logger.info( 'New TGs={}'.format(self._tg_filter) )
elif _cmd == 'dump_template': elif _cmd == 'dump_template':
self.dumpTemplate('PrivateVoice.bin') self.dumpTemplate('PrivateVoice.bin')
elif _cmd == 'get_info': elif _cmd == 'get_alias':
self._sock.sendto('reply dmr_info {} {} {} {}'.format(self._currentNetwork, self._sock.sendto('reply dmr_info {} {} {} {}'.format(self._currentNetwork,
int_id(NETWORK[self._currentNetwork]['LOCAL']['RADIO_ID']), int_id(self._CONFIG[self._currentNetwork]['LOCAL']['RADIO_ID']),
self._gateway_dmr_id, self._gateway_dmr_id,
get_subscriber_info(hex_str_3(self._gateway_dmr_id))), (self._dmrgui, 34003)) get_subscriber_info(hex_str_3(self._gateway_dmr_id))), (self._dmrgui, 34003))
elif _cmd == 'eval': elif _cmd == 'eval':
@ -614,11 +616,80 @@ class ambeIPSC(IPSC):
_ambe = _frame[33:52] _ambe = _frame[33:52]
print('SLOT2:', h(_frame)) print('SLOT2:', h(_frame))
print("pt={:02X} pid={} seq={:02X} src={} dst={} ct={:02X} uk={} ci={} rsq={}".format(_packettype, _peerid,_ipsc_seq, _src_sub,_dst_sub,_call_type,_call_ctrl_info,_call_info,_rtp_seq)) print("pt={:02X} pid={} seq={:02X} src={} dst={} ct={:02X} uk={} ci={} rsq={}".format(_packettype, _peerid,_ipsc_seq, _src_sub,_dst_sub,_call_type,_call_ctrl_info,_call_info,_rtp_seq))
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import os
import sys
import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: # ID ALIAS CREATION
networks[ipsc_network] = ambeIPSC(ipsc_network) # Download
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP']) if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = ambeIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
reactor.run() reactor.run()

332
bridge.py
View File

@ -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.
@ -46,14 +46,17 @@
from __future__ import print_function from __future__ import print_function
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet import task from twisted.internet import task
from binascii import b2a_hex as h from binascii import b2a_hex as ahex
from time import time from time import time
from importlib import import_module
# For debugging
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 dmr_utils.utils import hex_str_3, hex_str_4, int_id
from dmrlink import IPSC, systems, config_reports
from ipsc.ipsc_const import BURST_DATA_TYPE
__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 +66,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
@ -81,79 +75,87 @@ TS_CLEAR_TIME = .2
# configuration file and listed as "active". It can be empty, # configuration file and listed as "active". It can be empty,
# but it has to exist. # but it has to exist.
# #
try: def build_rules(_bridge_rules):
from bridge_rules import RULES as RULES_FILE try:
logger.info('Bridge rules file found and rules imported') rule_file = import_module(_bridge_rules)
except ImportError: logger.info('Bridge rules file found and rules imported')
sys.exit('Bridging rules file not found or invalid') except ImportError:
sys.exit('Bridging rules file not found or invalid')
# Convert integer GROUP ID numbers from the config into hex strings # Convert integer GROUP ID numbers from the config into hex strings
# we need to send in the actual data packets. # we need to send in the actual data packets.
# #
for _ipsc in RULES_FILE: for _ipsc in rule_file.RULES:
for _rule in RULES_FILE[_ipsc]['GROUP_VOICE']: for _rule in rule_file.RULES[_ipsc]['GROUP_VOICE']:
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP']) _rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP']) _rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
_rule['SRC_TS'] = _rule['SRC_TS'] _rule['SRC_TS'] = _rule['SRC_TS']
_rule['DST_TS'] = _rule['DST_TS'] _rule['DST_TS'] = _rule['DST_TS']
for i, e in enumerate(_rule['ON']): for i, e in enumerate(_rule['ON']):
_rule['ON'][i] = hex_str_3(_rule['ON'][i]) _rule['ON'][i] = hex_str_3(_rule['ON'][i])
for i, e in enumerate(_rule['OFF']): for i, e in enumerate(_rule['OFF']):
_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 CONFIG['SYSTEMS']:
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 CONFIG['SYSTEMS']:
if _ipsc not in RULES_FILE: if _ipsc not in rule_file.RULES:
sys.exit('ERROR: Bridge rules not found for all IPSC network configured') sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
RULES = RULES_FILE return rule_file.RULES
# Import List of Bridges # Import List of Bridges
# This is how we identify known bridges. If one of these is present # This is how we identify known bridges. If one of these is present
# and it's mode byte is set to bridge, we don't # and it's mode byte is set to bridge, we don't
# #
try: def build_bridges(_known_bridges):
from known_bridges import BRIDGES try:
logger.info('Known bridges file found and bridge ID list imported ') bridges_file = import_module(_known_bridges)
except ImportError: logger.info('Known bridges file found and bridge ID list imported ')
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled') return bridges_file.BRIDGES
BRIDGES = [] except ImportError:
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
return []
# Import subscriber ACL # Import subscriber ACL
# ACL may be a single list of subscriber IDs # ACL may be a single list of subscriber IDs
# Global action is to allow or deny them. Multiple lists with different actions and ranges # Global action is to allow or deny them. Multiple lists with different actions and ranges
# are not yet implemented. # are not yet implemented.
try: def build_acl(_sub_acl):
from sub_acl import ACL_ACTION, ACL try:
# uses more memory to build hex strings, but processes MUCH faster when checking for matches acl_file = import_module(_sub_acl)
for i, e in enumerate(ACL): for i, e in enumerate(acl_file.ACL):
ACL[i] = hex_str_3(ACL[i]) acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
logger.info('Subscriber access control file found, subscriber ACL imported') logger.info('ACL file found and ACL entries imported')
except ImportError: except ImportError:
logger.critical('\'sub_acl.py\' not found - all subscriber IDs are valid') logger.info('ACL file not found or invalid - all subscriber IDs are valid')
ACL_ACTION = 'NONE' ACL_ACTION = 'NONE'
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one) # Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
# define a differnet function to be used to check the ACL # define a differnet function to be used to check the ACL
if ACL_ACTION == 'PERMIT': global allow_sub
def allow_sub(_sub): if acl_file.ACL_ACTION == 'PERMIT':
if _sub in ACL: def allow_sub(_sub):
if _sub in ACL:
return True
else:
return False
elif acl_file.ACL_ACTION == 'DENY':
def allow_sub(_sub):
if _sub not in ACL:
return True
else:
return False
else:
def allow_sub(_sub):
return True return True
else:
return False return acl_file.ACL
elif ACL_ACTION == 'DENY':
def allow_sub(_sub):
if _sub not in ACL:
return True
else:
return False
else:
def allow_sub(_sub):
return True
# Run this every minute for rule timer updates # Run this every minute for rule timer updates
def rule_timer_loop(): def rule_timer_loop():
logger.debug('(ALL IPSC) Rule timer loop started') logger.debug('(ALL IPSC) Rule timer loop started')
@ -179,15 +181,17 @@ def rule_timer_loop():
else: else:
logger.debug('Rule timer loop made no rule changes') logger.debug('Rule timer loop made no rule changes')
class bridgeIPSC(IPSC): class bridgeIPSC(IPSC):
def __init__(self, *args, **kwargs): def __init__(self, _name, _config, _logger, _bridges):
IPSC.__init__(self, *args, **kwargs) IPSC.__init__(self, _name, _config, _logger)
if BRIDGES: self.BRIDGES = _bridges
logger.info('Initializing backup/polite bridging') if self.BRIDGES:
self._logger.info('(%s) Initializing backup/polite bridging', self._system)
self.BRIDGE = False self.BRIDGE = False
else: else:
self.BRIDGE = True self.BRIDGE = True
logger.info('Initializing standard bridging') self._logger.info('Initializing standard bridging')
self.IPSC_STATUS = { self.IPSC_STATUS = {
1: {'RX_GROUP':'\x00', 'TX_GROUP':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'}, 1: {'RX_GROUP':'\x00', 'TX_GROUP':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'},
@ -199,31 +203,32 @@ class bridgeIPSC(IPSC):
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer) # Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
if BRIDGES:
def startProtocol(self): def startProtocol(self):
IPSC.startProtocol(self) IPSC.startProtocol(self)
if self.BRIDGES:
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop) self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER']) self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
# This is the backup/polite bridge maintenance loop # This is the backup/polite bridge maintenance loop
def bridge_presence_loop(self): def bridge_presence_loop(self):
self._logger.debug('(%s) Bridge presence loop initiated', self._system)
_temp_bridge = True _temp_bridge = True
for peer in BRIDGES: for peer in self.BRIDGES:
_peer = hex_str_4(peer) _peer = hex_str_4(peer)
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']): if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
_temp_bridge = False _temp_bridge = False
logger.debug('(%s) Peer %s is an active bridge', self._network, int_id(_peer)) self._logger.debug('(%s) Peer %s is an active bridge', self._system, int_id(_peer))
if _peer == self._master['RADIO_ID'] \ if _peer == self._master['RADIO_ID'] \
and self._master['STATUS']['CONNECTED'] \ and self._master['STATUS']['CONNECTED'] \
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']): and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
_temp_bridge = False _temp_bridge = False
logger.debug('(%s) Master %s is an active bridge',self._network, int_id(_peer)) self._logger.debug('(%s) Master %s is an active bridge',self._system, int_id(_peer))
if self.BRIDGE != _temp_bridge: if self.BRIDGE != _temp_bridge:
logger.info('(%s) Changing bridge status to: %s', self._network, _temp_bridge ) self._logger.info('(%s) Changing bridge status to: %s', self._system, _temp_bridge )
self.BRIDGE = _temp_bridge self.BRIDGE = _temp_bridge
@ -231,32 +236,31 @@ class bridgeIPSC(IPSC):
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************ #************************************************
# #
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data): def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
# Check for ACL match, and return if the subscriber is not allowed # Check for ACL match, and return if the subscriber is not allowed
if allow_sub(_src_sub) == False: if allow_sub(_src_sub) == False:
logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group)) self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
return return
# Process the packet # Process the packet
logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group)) self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types) _burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
_seq_id = _data[5] _seq_id = _data[5]
_ts += 1
now = time() # Mark packet arrival time -- we'll need this for call contention handling now = time() # Mark packet arrival time -- we'll need this for call contention handling
for rule in RULES[_network]['GROUP_VOICE']: for rule in RULES[self._system]['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
# #
# If this is an inter-DMRlink trunk, this isn't necessary # If this is an inter-DMRlink trunk, this isn't necessary
if RULES[_network]['TRUNK'] == False: if RULES[self._system]['TRUNK'] == False:
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is: # The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime # From a different group than last RX from this IPSC, but it has been less than Group Hangtime
@ -265,27 +269,26 @@ class bridgeIPSC(IPSC):
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time # From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules # The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
# #
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[_network]['GROUP_HANGTIME'])): if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP'])) self._logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
continue continue
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[_network]['GROUP_HANGTIME'])): if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP'])) self._logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
continue continue
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < TS_CLEAR_TIME): if (rule['DST_GROUP'] == _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP'])) self._logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
continue continue
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['TX_GROUP']) and (_src_sub != _status[rule['DST_TS']]['TX_SRC_SUB']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < TS_CLEAR_TIME): if (rule['DST_GROUP'] == _status[rule['DST_TS']]['TX_GROUP']) and (_src_sub != _status[rule['DST_TS']]['TX_SRC_SUB']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']: if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', _network, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB'])) self._logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', self._system, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
continue continue
# #
# END CONTENTION HANDLING # END CONTENTION HANDLING
# #
# #
# BEGIN FRAME FORWARDING # BEGIN FRAME FORWARDING
# #
@ -293,7 +296,7 @@ class bridgeIPSC(IPSC):
_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, self._CONFIG['SYSTEMS'][_target]['LOCAL']['RADIO_ID'])
# Re-Write the destination Group ID # Re-Write the destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP']) _tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
@ -322,7 +325,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
# #
@ -350,105 +353,158 @@ class bridgeIPSC(IPSC):
if self.last_seq_id != _seq_id: if self.last_seq_id != _seq_id:
self.last_seq_id = _seq_id self.last_seq_id = _seq_id
self.call_start = time() self.call_start = time()
logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)) self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
# Action happens on un-key # Action happens on un-key
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']: if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
if self.last_seq_id == _seq_id: if self.last_seq_id == _seq_id:
self.call_duration = time() - self.call_start self.call_duration = time() - self.call_start
logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration) self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
else: else:
logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),) self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
# Iterate the rules dictionary # Iterate the rules dictionary
for rule in RULES[_network]['GROUP_VOICE']: for rule in RULES[self._system]['GROUP_VOICE']:
_target = rule['DST_NET'] _target = rule['DST_NET']
# TGID matches a rule source, reset its timer # TGID matches a rule source, reset its timer
if _ts == rule['SRC_TS'] and _dst_group == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)): if _ts == rule['SRC_TS'] and _dst_group == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
rule['TIMER'] = now + rule['TIMEOUT'] rule['TIMER'] = now + rule['TIMEOUT']
logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', _network, rule['NAME'], rule['TIMER']) self._logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER'])
# Scan for reciprocal rules and reset their timers as well. # Scan for reciprocal rules and reset their timers as well.
for target_rule in RULES[_target]['GROUP_VOICE']: for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']: if target_rule['NAME'] == rule['NAME']:
target_rule['TIMER'] = now + target_rule['TIMEOUT'] target_rule['TIMER'] = now + target_rule['TIMEOUT']
logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', _network, target_rule['NAME'], _target, rule['TIMER']) self._logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER'])
# TGID matches an ACTIVATION trigger # TGID matches an ACTIVATION trigger
if _dst_group in rule['ON']: if _dst_group in rule['ON']:
# Set the matching rule as ACTIVE # Set the matching rule as ACTIVE
rule['ACTIVE'] = True rule['ACTIVE'] = True
rule['TIMER'] = now + rule['TIMEOUT'] rule['TIMER'] = now + rule['TIMEOUT']
logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE']) self._logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
# Set reciprocal rules for other IPSCs as ACTIVE # Set reciprocal rules for other IPSCs as ACTIVE
for target_rule in RULES[_target]['GROUP_VOICE']: for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']: if target_rule['NAME'] == rule['NAME']:
target_rule['ACTIVE'] = True target_rule['ACTIVE'] = True
target_rule['TIMER'] = now + target_rule['TIMEOUT'] target_rule['TIMER'] = now + target_rule['TIMEOUT']
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE']) self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
# TGID matches an DE-ACTIVATION trigger # TGID matches an DE-ACTIVATION trigger
if _dst_group in rule['OFF']: if _dst_group in rule['OFF']:
# Set the matching rule as ACTIVE # Set the matching rule as ACTIVE
rule['ACTIVE'] = False rule['ACTIVE'] = False
logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE']) self._logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
# Set reciprocal rules for other IPSCs as ACTIVE # Set reciprocal rules for other IPSCs as ACTIVE
_target = rule['DST_NET'] _target = rule['DST_NET']
for target_rule in RULES[_target]['GROUP_VOICE']: for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']: if target_rule['NAME'] == rule['NAME']:
target_rule['ACTIVE'] = False target_rule['ACTIVE'] = False
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE']) self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
# #
# END IN-BAND SIGNALLING # END IN-BAND SIGNALLING
# #
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub)) self._logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
for target in RULES[_network]['GROUP_DATA']: for target in RULES[self._system]['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, self._CONFIG[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, _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)) self._logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
for target in RULES[_network]['PRIVATE_DATA']: for target in RULES[self._system]['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, self._CONFIG[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__':
import argparse
import os
import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
config_reports(CONFIG)
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 # Shut ourselves down gracefully with the IPSC peers.
for ipsc_network in NETWORK: def sig_handler(_signal, _frame):
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
networks[ipsc_network] = bridgeIPSC(ipsc_network)
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# Build the routing rules file
RULES = build_rules('bridge_rules')
# Build list of known bridge IDs
BRIDGES = build_bridges('known_bridges')
# Build the Access Control List
ACL = build_acl('sub_acl')
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = bridgeIPSC(system, CONFIG, logger, BRIDGES)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED # INITIALIZE THE REPORTING LOOP IF CONFIGURED
if REPORTS['REPORT_NETWORKS']: if CONFIG['REPORTS']['REPORT_NETWORKS']:
reporting = task.LoopingCall(reporting_loop) reporting_loop = config_reports(CONFIG)
reporting.start(REPORTS['REPORT_INTERVAL']) reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED # INITIALIZE THE REPORTING LOOP IF CONFIGURED
rule_timer = task.LoopingCall(rule_timer_loop) rule_timer = task.LoopingCall(rule_timer_loop)
rule_timer.start(60) rule_timer.start(60)
reactor.run() reactor.run()

File diff suppressed because it is too large Load Diff

View File

@ -64,6 +64,23 @@ LOG_LEVEL: INFO
LOG_NAME: DMRlink LOG_NAME: DMRlink
# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES
# Ok, not the TGID, there's no master list I know of to download
# This is intended as a facility for other applcations built on top of
# HBlink to use, and will NOT be used in HBlink directly.
# STALE_DAYS is the number of days since the last download before we
# download again. Don't be an ass and change this to less than a few days.
[ALIASES]
TRY_DOWNLOAD: True
PATH: ./
PEER_FILE: peer_ids.csv
SUBSCRIBER_FILE: subscriber_ids.csv
TGID_FILE: talkgroup_ids.csv
PEER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=repeaters&format=csv&header=0
SUBSCRIBER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=users&format=csv&header=0
STALE_DAYS: 7
# CONFIGURATION FOR IPSC NETWORKS # CONFIGURATION FOR IPSC NETWORKS
# Please read these closely - catastrophic results could result by setting # Please read these closely - catastrophic results could result by setting
# certain flags for things DMRlink cannot do. # certain flags for things DMRlink cannot do.

216
dmrlink_config.py Executable file
View File

@ -0,0 +1,216 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
import ConfigParser
import sys
from socket import gethostbyname
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
def build_config(_config_file):
config = ConfigParser.ConfigParser()
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['ALIASES'] = {}
CONFIG['SYSTEMS'] = {}
try:
for section in config.sections():
if section == 'GLOBAL':
CONFIG['GLOBAL'].update({
'PATH': config.get(section, 'PATH')
})
elif section == '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':
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 section == 'ALIASES':
CONFIG['ALIASES'].update({
'TRY_DOWNLOAD': config.getboolean(section, 'TRY_DOWNLOAD'),
'PATH': config.get(section, 'PATH'),
'PEER_FILE': config.get(section, 'PEER_FILE'),
'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'),
'TGID_FILE': config.get(section, 'TGID_FILE'),
'PEER_URL': config.get(section, 'PEER_URL'),
'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'),
'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400,
})
elif config.getboolean(section, 'ENABLED'):
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'),
# 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
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
# Used to run this file direclty and print the config,
# which might be useful for debugging
if __name__ == '__main__':
import sys
import os
import argparse
from pprint import pprint
# 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='CONFIG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
cli_args = parser.parse_args()
# Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
if not cli_args.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
pprint(build_config(cli_args.CONFIG_FILE))

87
dmrlink_log.py Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
###############################################################################
import logging
from logging.config import dictConfig
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
def config_logging(_logger):
dictConfig({
'version': 1,
'disable_existing_loggers': False,
'filters': {
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'timed': {
'format': '%(levelname)s %(asctime)s %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
'syslog': {
'format': '%(name)s (%(process)d): %(levelname)s %(message)s'
}
},
'handlers': {
'null': {
'class': 'logging.NullHandler'
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'console-timed': {
'class': 'logging.StreamHandler',
'formatter': 'timed'
},
'file': {
'class': 'logging.FileHandler',
'formatter': 'simple',
'filename': _logger['LOG_FILE'],
},
'file-timed': {
'class': 'logging.FileHandler',
'formatter': 'timed',
'filename': _logger['LOG_FILE'],
},
'syslog': {
'class': 'logging.handlers.SysLogHandler',
'formatter': 'syslog',
}
},
'loggers': {
_logger['LOG_NAME']: {
'handlers': _logger['LOG_HANDLERS'].split(','),
'level': _logger['LOG_LEVEL'],
'propagate': True,
}
}
})
return logging.getLogger(_logger['LOG_NAME'])

View File

@ -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

129
log.py
View File

@ -25,7 +25,8 @@ from twisted.internet import reactor
from binascii import b2a_hex as h from binascii import b2a_hex as h
import time import time
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger from dmrlink import IPSC, systems
from dmr_utils.utils import hex_str_3, hex_str_4, int_id, get_alias
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -36,39 +37,33 @@ __email__ = 'n0mjs@me.com'
class logIPSC(IPSC): class logIPSC(IPSC):
def __init__(self, _name, _config, _logger):
def __init__(self, *args, **kwargs): IPSC.__init__(self, _name, _config, _logger)
IPSC.__init__(self, *args, **kwargs)
self.ACTIVE_CALLS = [] self.ACTIVE_CALLS = []
#************************************************ #************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************ #************************************************
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
# _log = logger.debug
if (_ts not in self.ACTIVE_CALLS) or _end: if (_ts not in self.ACTIVE_CALLS) or _end:
_time = time.strftime('%m/%d/%y %H:%M:%S') _time = time.strftime('%m/%d/%y %H:%M:%S')
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids) _dst_sub = get_alias(_dst_sub, talkgroup_ids)
_peerid = get_info(int_id(_peerid), peer_ids) _peerid = get_alias(_peerid, peer_ids)
_src_sub = get_info(int_id(_src_sub), subscriber_ids) _src_sub = get_alias(_src_sub, subscriber_ids)
if not _end: self.ACTIVE_CALLS.append(_ts) if not _end: self.ACTIVE_CALLS.append(_ts)
if _end: self.ACTIVE_CALLS.remove(_ts) if _end: self.ACTIVE_CALLS.remove(_ts)
if _ts: _ts = 2
else: _ts = 1
if _end: _end = 'END' if _end: _end = 'END'
else: _end = 'START' else: _end = 'START'
print('{} ({}) Call {} Group Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, _network, _end, _peerid, _src_sub, _dst_sub, _ts)) print('{} ({}) Call {} Group Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, self._system, _end, _peerid, _src_sub, _dst_sub, _ts))
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
# _log = logger.debug
if (_ts not in self.ACTIVE_CALLS) or _end: if (_ts not in self.ACTIVE_CALLS) or _end:
_time = time.strftime('%m/%d/%y %H:%M:%S') _time = time.strftime('%m/%d/%y %H:%M:%S')
_dst_sub = get_info(int_id(_dst_sub), subscriber_ids) _dst_sub = get_alias(_dst_sub, subscriber_ids)
_peerid = get_info(int_id(_peerid), peer_ids) _peerid = get_alias(_peerid, peer_ids)
_src_sub = get_info(int_id(_src_sub), subscriber_ids) _src_sub = get_alias(_src_sub, subscriber_ids)
if not _end: self.ACTIVE_CALLS.append(_ts) if not _end: self.ACTIVE_CALLS.append(_ts)
if _end: self.ACTIVE_CALLS.remove(_ts) if _end: self.ACTIVE_CALLS.remove(_ts)
@ -77,25 +72,93 @@ class logIPSC(IPSC):
if _end: _end = 'END' if _end: _end = 'END'
else: _end = 'START' else: _end = 'START'
print('{} ({}) Call {} Private Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, _network, _end, _peerid, _src_sub, _dst_sub, _ts)) print('{} ({}) Call {} Private Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, self._system, _end, _peerid, _src_sub, _dst_sub, _ts))
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids) _dst_sub = get_alias(_dst_sub, talkgroup_ids)
_peerid = get_info(int_id(_peerid), peer_ids) _peerid = get_alias(_peerid, peer_ids)
_src_sub = get_info(int_id(_src_sub), subscriber_ids) _src_sub = get_alias(_src_sub, subscriber_ids)
print('({}) Group Data Packet Received From: {}' .format(_network, _src_sub)) print('({}) Group Data Packet Received From: {}' .format(self._system, _src_sub))
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
_dst_sub = get_info(int_id(_dst_sub), subscriber_ids) _dst_sub = get_alias(_dst_sub, subscriber_ids)
_peerid = get_info(int_id(_peerid), peer_ids) _peerid = get_alias(_peerid, peer_ids)
_src_sub = get_info(int_id(_src_sub), subscriber_ids) _src_sub = get_alias(_src_sub, subscriber_ids)
print('({}) Private Data Packet Received From: {} To: {}' .format(_network, _src_sub, _dst_sub)) print('({}) Private Data Packet Received From: {} To: {}' .format(self._system, _src_sub, _dst_sub))
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import os
import sys
import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: # ID ALIAS CREATION
networks[ipsc_network] = logIPSC(ipsc_network) # Download
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP']) if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = logIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
reactor.run() reactor.run()

File diff suppressed because it is too large Load Diff

View File

@ -32,11 +32,14 @@
from __future__ import print_function from __future__ import print_function
from twisted.internet import reactor from twisted.internet import reactor
from binascii import b2a_hex as h
import sys, time import sys, time
import cPickle as pickle import cPickle as pickle
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3
from dmrlink import IPSC, systems
from dmr_utils.utils import int_id, hex_str_3
from ipsc.ipsc_const import BURST_DATA_TYPE
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2014 - 2015 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2014 - 2015 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -45,14 +48,6 @@ __license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS' __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'
}
# path+filename for the transmission to play back # path+filename for the transmission to play back
filename = '../test.pickle' filename = '../test.pickle'
@ -66,9 +61,8 @@ trigger_groups_1 = ['\x00\x00\x01', '\x00\x00\x0D', '\x00\x00\x64']
trigger_groups_2 = ['\x00\x0C\x30',] trigger_groups_2 = ['\x00\x0C\x30',]
class playIPSC(IPSC): class playIPSC(IPSC):
def __init__(self, _name, _config, _logger):
def __init__(self, *args, **kwargs): IPSC.__init__(self, _name, _config, _logger)
IPSC.__init__(self, *args, **kwargs)
self.CALL_DATA = [] self.CALL_DATA = []
self.event_id = 1 self.event_id = 1
@ -76,30 +70,30 @@ class playIPSC(IPSC):
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************ #************************************************
# #
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data): def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
if _end: if _end:
_self_peer = NETWORK[_network]['LOCAL']['RADIO_ID'] _self_peer = self._config['LOCAL']['RADIO_ID']
_self_src = _self_peer[1:] _self_src = _self_peer[1:]
if (_peerid == _self_peer) or (_src_sub == _self_src): if (_peerid == _self_peer) or (_src_sub == _self_src):
logger.error('(%s) Just received a packet that appears to have been originated by us. PeerID: %s Subscriber: %s TS: %s, TGID: %s', _network, int_id(_peerid), int_id(_src_sub), int(_ts)+1, int_id(_dst_group)) self._logger.error('(%s) Just received a packet that appears to have been originated by us. PeerID: %s Subscriber: %s TS: %s, TGID: %s', self._system, int_id(_peerid), int_id(_src_sub), int(_ts), int_id(_dst_group))
return return
if trigger == False: if trigger == False:
if (_ts == 0 and _dst_group not in trigger_groups_1) or (_ts == 1 and _dst_group not in trigger_groups_2): if (_ts == 1 and _dst_group not in trigger_groups_1) or (_ts == 2 and _dst_group not in trigger_groups_2):
return return
else: else:
if (_ts == 0 and _dst_group in trigger_groups_1) or (_ts == 1 and _dst_group in trigger_groups_2): if (_ts == 1 and _dst_group not in trigger_groups_1) or (_ts == 2 and _dst_group not in trigger_groups_2):
return return
logger.info('(%s) Event ID: %s - Playback triggered from SourceID: %s, TS: %s, TGID: %s, PeerID: %s', _network, self.event_id, int_id(_src_sub), _ts+1, int_id(_dst_group), int_id(_peerid)) self._logger.info('(%s) Event ID: %s - Playback triggered from SourceID: %s, TS: %s, TGID: %s, PeerID: %s', self._system, self.event_id, int_id(_src_sub), _ts, int_id(_dst_group), int_id(_peerid))
# Determine the type of voice packet this is (see top of file for possible types) # Determine the type of voice packet this is (see top of file for possible types)
_burst_data_type = _data[30] _burst_data_type = _data[30]
time.sleep(2) time.sleep(2)
self.CALL_DATA = pickle.load(open(filename, 'rb')) self.CALL_DATA = pickle.load(open(filename, 'rb'))
logger.info('(%s) Event ID: %s - Playing back file: %s', _network, self.event_id, filename) self._logger.info('(%s) Event ID: %s - Playing back file: %s', self._system, self.event_id, filename)
for i in self.CALL_DATA: for i in self.CALL_DATA:
_tmp_data = i _tmp_data = i
@ -113,9 +107,9 @@ class playIPSC(IPSC):
# Re-Write IPSC timeslot value # Re-Write IPSC timeslot value
_call_info = int_id(_tmp_data[17:18]) _call_info = int_id(_tmp_data[17:18])
if _ts == 0: if _ts == 1:
_call_info &= ~(1 << 5) _call_info &= ~(1 << 5)
elif _ts == 1: elif _ts == 2:
_call_info |= 1 << 5 _call_info |= 1 << 5
_call_info = chr(_call_info) _call_info = chr(_call_info)
_tmp_data = _tmp_data[:17] + _call_info + _tmp_data[18:] _tmp_data = _tmp_data[:17] + _call_info + _tmp_data[18:]
@ -124,9 +118,9 @@ class playIPSC(IPSC):
# Determine if the slot is present, so we can translate if need be # Determine if the slot is present, so we can translate if need be
if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']: if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']:
# Re-Write timeslot if necessary... # Re-Write timeslot if necessary...
if _ts == 0: if _ts == 1:
_burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE'] _burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE']
elif _ts == 1: elif _ts == 2:
_burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE'] _burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE']
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:] _tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
@ -134,13 +128,58 @@ class playIPSC(IPSC):
self.send_to_ipsc(_tmp_data) self.send_to_ipsc(_tmp_data)
time.sleep(0.06) time.sleep(0.06)
self.CALL_DATA = [] self.CALL_DATA = []
logger.info('(%s) Event ID: %s - Playback Completed', _network, self.event_id) self._logger.info('(%s) Event ID: %s - Playback Completed', self._system, self.event_id)
self.event_id = self.event_id + 1 self.event_id = self.event_id + 1
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import os
import sys
import signal
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: # Shut ourselves down gracefully with the IPSC peers.
networks[ipsc_network] = playIPSC(ipsc_network) def sig_handler(_signal, _frame):
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP']) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
reactor.run()
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = playIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
reactor.run()

View File

@ -22,10 +22,11 @@
from __future__ import print_function from __future__ import print_function
from twisted.internet import reactor from twisted.internet import reactor
from binascii import b2a_hex as h from binascii import b2a_hex as ahex
import sys, time import sys, time
from dmrlink import IPSC, NETWORK, networks, logger, dmr_nat, int_id, hex_str_3 from dmrlink import IPSC, systems
from dmr_utils.utils import int_id, hex_str_3
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -43,62 +44,66 @@ except ImportError:
HEX_TGID = hex_str_3(TGID) HEX_TGID = hex_str_3(TGID)
HEX_SUB = hex_str_3(SUB) HEX_SUB = hex_str_3(SUB)
BOGUS_SUB = '\xFF\xFF\xFF' BOGUS_SUB = '\xFF\xFF\xFF'
if GROUP_SRC_SUB:
logger.info('Playback: USING SUBSCRIBER ID: %s FOR GROUP REPEAT', GROUP_SRC_SUB)
HEX_GRP_SUB = hex_str_3(GROUP_SRC_SUB)
class playbackIPSC(IPSC): class playbackIPSC(IPSC):
def __init__(self, _name, _config, _logger):
def __init__(self, *args, **kwargs): IPSC.__init__(self, _name, _config, _logger)
IPSC.__init__(self, *args, **kwargs)
self.CALL_DATA = [] self.CALL_DATA = []
if GROUP_SRC_SUB:
self._logger.info('Playback: USING SUBSCRIBER ID: %s FOR GROUP REPEAT', GROUP_SRC_SUB)
self.GROUP_SRC_SUB = hex_str_3(GROUP_SRC_SUB)
if GROUP_REPEAT:
self._logger.info('Playback: GROUP REPEAT ENABLED')
if PRIVATE_REPEAT:
self._logger.info('Playback: PRIVATE REPEAT ENABLED')
#************************************************ #************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************ #************************************************
# #
if GROUP_REPEAT: if GROUP_REPEAT:
logger.info('Playback: DEFINING GROUP REPEAT FUNCTION') def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
if HEX_TGID == _dst_sub and _ts in GROUP_TS: if HEX_TGID == _dst_sub and _ts in GROUP_TS:
if not _end: if not _end:
if not self.CALL_DATA: if not self.CALL_DATA:
logger.info('(%s) Receiving transmission to be played back from subscriber: %s', _network, int_id(_src_sub)) self._logger.info('(%s) Receiving transmission to be played back from subscriber: %s', self._system, int_id(_src_sub))
_tmp_data = _data _tmp_data = _data
#_tmp_data = dmr_nat(_data, _src_sub, NETWORK[_network]['LOCAL']['RADIO_ID']) #_tmp_data = dmr_nat(_data, _src_sub, self._config['LOCAL']['RADIO_ID'])
self.CALL_DATA.append(_tmp_data) self.CALL_DATA.append(_tmp_data)
if _end: if _end:
self.CALL_DATA.append(_data) self.CALL_DATA.append(_data)
time.sleep(2) time.sleep(2)
logger.info('(%s) Playing back transmission from subscriber: %s', _network, int_id(_src_sub)) self._logger.info('(%s) Playing back transmission from subscriber: %s', self._system, int_id(_src_sub))
for i in self.CALL_DATA: for i in self.CALL_DATA:
_tmp_data = i _tmp_data = i
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID']) _tmp_data = _tmp_data.replace(_peerid, self._config['LOCAL']['RADIO_ID'])
if GROUP_SRC_SUB: if GROUP_SRC_SUB:
_tmp_data = _tmp_data.replace(_src_sub, HEX_GRP_SUB) _tmp_data = _tmp_data.replace(_src_sub, self.GROUP_SRC_SUB)
# Send the packet to all peers in the target IPSC # Send the packet to all peers in the target IPSC
self.send_to_ipsc(_tmp_data) self.send_to_ipsc(_tmp_data)
time.sleep(0.06) time.sleep(0.06)
self.CALL_DATA = [] self.CALL_DATA = []
if PRIVATE_REPEAT: if PRIVATE_REPEAT:
logger.info('Playback: DEFINING PRIVATE REPEAT FUNCTION') def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
if HEX_SUB == _dst_sub and _ts in PRIVATE_TS: if HEX_SUB == _dst_sub and _ts in PRIVATE_TS:
if not _end: if not _end:
if not self.CALL_DATA: if not self.CALL_DATA:
logger.info('(%s) Receiving transmission to be played back from subscriber: %s, to subscriber: %s', _network, int_id(_src_sub), int_id(_dst_sub)) self._logger.info('(%s) Receiving transmission to be played back from subscriber: %s, to subscriber: %s', self._system, int_id(_src_sub), int_id(_dst_sub))
_tmp_data = _data _tmp_data = _data
self.CALL_DATA.append(_tmp_data) self.CALL_DATA.append(_tmp_data)
if _end: if _end:
self.CALL_DATA.append(_data) self.CALL_DATA.append(_data)
time.sleep(1) time.sleep(1)
logger.info('(%s) Playing back transmission from subscriber: %s, to subscriber %s', _network, int_id(_src_sub), int_id(_dst_sub)) self._logger.info('(%s) Playing back transmission from subscriber: %s, to subscriber %s', self._system, int_id(_src_sub), int_id(_dst_sub))
_orig_src = _src_sub _orig_src = _src_sub
_orig_dst = _dst_sub _orig_dst = _dst_sub
for i in self.CALL_DATA: for i in self.CALL_DATA:
_tmp_data = i _tmp_data = i
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID']) _tmp_data = _tmp_data.replace(_peerid, self._config['LOCAL']['RADIO_ID'])
_tmp_data = _tmp_data.replace(_dst_sub, BOGUS_SUB) _tmp_data = _tmp_data.replace(_dst_sub, BOGUS_SUB)
_tmp_data = _tmp_data.replace(_src_sub, _orig_dst) _tmp_data = _tmp_data.replace(_src_sub, _orig_dst)
_tmp_data = _tmp_data.replace(BOGUS_SUB, _orig_src) _tmp_data = _tmp_data.replace(BOGUS_SUB, _orig_src)
@ -107,10 +112,55 @@ class playbackIPSC(IPSC):
time.sleep(0.06) time.sleep(0.06)
self.CALL_DATA = [] self.CALL_DATA = []
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import os
import sys
import signal
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: # Shut ourselves down gracefully with the IPSC peers.
networks[ipsc_network] = playbackIPSC(ipsc_network) def sig_handler(_signal, _frame):
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP']) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = playbackIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
reactor.run() reactor.run()

115
rcm.py
View File

@ -26,12 +26,13 @@ from __future__ import print_function
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 binascii import b2a_hex as h from binascii import b2a_hex as ahex
import datetime import datetime
import binascii import binascii
import dmrlink import dmrlink
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger from dmrlink import IPSC, systems
from dmr_utils.utils import get_alias, int_id
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -51,15 +52,14 @@ rpt = True
nack = True nack = True
class rcmIPSC(IPSC): class rcmIPSC(IPSC):
def __init__(self, _name, _config, _logger):
def __init__(self, *args, **kwargs): IPSC.__init__(self, _name, _config, _logger)
IPSC.__init__(self, *args, **kwargs)
#************************************************ #************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES # CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************ #************************************************
# #
def call_mon_status(self, _network, _data): def call_mon_status(self, _data):
if not status: if not status:
return return
_source = _data[1:5] _source = _data[1:5]
@ -73,19 +73,19 @@ class rcmIPSC(IPSC):
_prio = _data[23] _prio = _data[23]
_sec = _data[24] _sec = _data[24]
_source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids)) _source = str(int_id(_source)) + ', ' + str(get_alias(_source, peer_ids))
_ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_info(int_id(_ipsc_src), peer_ids)) _ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_alias(_ipsc_src, peer_ids))
_rf_src = str(int_id(_rf_src)) + ', ' + str(get_info(int_id(_rf_src), subscriber_ids)) _rf_src = str(int_id(_rf_src)) + ', ' + str(get_alias(_rf_src, subscriber_ids))
if _type == '\x4F' or '\x51': if _type == '\x4F' or '\x51':
_rf_tgt = 'TGID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_info(int_id(_rf_tgt), talkgroup_ids)) _rf_tgt = 'TGID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_alias(_rf_tgt, talkgroup_ids))
else: else:
_rf_tgt = 'SID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_info(int_id(_rf_tgt), subscriber_ids)) _rf_tgt = 'SID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_alias(_rf_tgt, subscriber_ids))
print('Call Monitor - Call Status') print('Call Monitor - Call Status')
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
print('DATA SOURCE: ', _source) print('DATA SOURCE: ', _source)
print('IPSC: ', _network) print('IPSC: ', self._system)
print('IPSC Source: ', _ipsc_src) print('IPSC Source: ', _ipsc_src)
print('Timeslot: ', TS[_ts]) print('Timeslot: ', TS[_ts])
try: try:
@ -100,14 +100,14 @@ class rcmIPSC(IPSC):
print('Target Sub: ', _rf_tgt) print('Target Sub: ', _rf_tgt)
print() print()
def call_mon_rpt(self, _network, _data): def call_mon_rpt(self, _data):
if not rpt: if not rpt:
return return
_source = _data[1:5] _source = _data[1:5]
_ts1_state = _data[5] _ts1_state = _data[5]
_ts2_state = _data[6] _ts2_state = _data[6]
_source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids)) _source = str(int_id(_source)) + ', ' + str(get_alias(_source, peer_ids))
print('Call Monitor - Repeater State') print('Call Monitor - Repeater State')
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
@ -123,13 +123,13 @@ class rcmIPSC(IPSC):
print('TS2 State (unknown): ', h(_ts2_state)) print('TS2 State (unknown): ', h(_ts2_state))
print() print()
def call_mon_nack(self, _network, _data): def call_mon_nack(self, _data):
if not nack: if not nack:
return return
_source = _data[1:5] _source = _data[1:5]
_nack = _data[5] _nack = _data[5]
_source = get_info(int_id(_source), peer_ids) _source = get_alias(_source, peer_ids)
print('Call Monitor - Transmission NACK') print('Call Monitor - Transmission NACK')
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")) print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
@ -140,17 +140,84 @@ class rcmIPSC(IPSC):
print('NACK Cause (unknown): ', h(_nack)) print('NACK Cause (unknown): ', h(_nack))
print() print()
def repeater_wake_up(self, _network, _data): def repeater_wake_up(self, _data):
_source = _data[1:5] _source = _data[1:5]
_source_dec = int_id(_source) _source_name = get_alias(_source, peer_ids)
_source_name = get_info(_source_dec, peer_ids) print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(self._system, _source_name, int_id(_source)))
#print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(_network, _source_name, _source_dec))
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import os
import sys
import signal
from dmr_utils.utils import try_download, mk_id_dict
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: # ID ALIAS CREATION
networks[ipsc_network] = rcmIPSC(ipsc_network) # Download
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP']) if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
# Try updating peer aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Try updating subscriber aliases file
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
logger.info(result)
# Make Dictionaries
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
if peer_ids:
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
if subscriber_ids:
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
if talkgroup_ids:
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
# Shut ourselves down gracefully with the IPSC peers.
def sig_handler(_signal, _frame):
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = rcmIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
reactor.run() reactor.run()

View File

@ -27,7 +27,8 @@ from binascii import b2a_hex as h
import sys import sys
import cPickle as pickle import cPickle as pickle
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3 from dmrlink import IPSC, systems
from dmr_utils.utils import hex_str_3, int_id
__author__ = 'Cortney T. Buffington, N0MJS' __author__ = 'Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group' __copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
@ -49,11 +50,11 @@ while True:
ts = raw_input('Which timeslot (1, 2 or \'both\')? ') ts = raw_input('Which timeslot (1, 2 or \'both\')? ')
if ts == '1' or ts == '2' or ts =='both': if ts == '1' or ts == '2' or ts =='both':
if ts == '1': if ts == '1':
ts = (0,)
if ts == '2':
ts = (1,) ts = (1,)
if ts == '2':
ts = (2,)
if ts == 'both': if ts == 'both':
ts = (0,1) ts = (1,2)
break break
print('...input must be \'1\', \'2\' or \'both\'') print('...input must be \'1\', \'2\' or \'both\'')
@ -64,9 +65,8 @@ id = hex_str_3(id)
filename = raw_input('Filename to use for this recording? ') filename = raw_input('Filename to use for this recording? ')
class recordIPSC(IPSC): class recordIPSC(IPSC):
def __init__(self, _name, _config, _logger):
def __init__(self, *args, **kwargs): IPSC.__init__(self, _name, _config, _logger)
IPSC.__init__(self, *args, **kwargs)
self.CALL_DATA = [] self.CALL_DATA = []
#************************************************ #************************************************
@ -75,39 +75,83 @@ class recordIPSC(IPSC):
# #
if tx_type == 'g': if tx_type == 'g':
print('Initializing to record GROUP VOICE transmission') print('Initializing to record GROUP VOICE transmission')
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
if id == _dst_sub and _ts in ts: if id == _dst_sub and _ts in ts:
if not _end: if not _end:
if not self.CALL_DATA: if not self.CALL_DATA:
print('({}) Recording transmission from subscriber: {}' .format(_network, int_id(_src_sub))) print('({}) Recording transmission from subscriber: {}' .format(self._system, int_id(_src_sub)))
self.CALL_DATA.append(_data) self.CALL_DATA.append(_data)
if _end: if _end:
self.CALL_DATA.append(_data) self.CALL_DATA.append(_data)
print('({}) Transmission ended, writing to disk: {}' .format(_network, filename)) print('({}) Transmission ended, writing to disk: {}' .format(self._system, filename))
pickle.dump(self.CALL_DATA, open(filename, 'wb')) pickle.dump(self.CALL_DATA, open(filename, 'wb'))
reactor.stop() reactor.stop()
print('Recording created, program terminating') print('Recording created, program terminating')
if tx_type == 'p': if tx_type == 'p':
print('Initializing ro record PRIVATE VOICE transmission') print('Initializing ro record PRIVATE VOICE transmission')
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data): def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
if id == _dst_sub and _ts in ts: if id == _dst_sub and _ts in ts:
if not _end: if not _end:
if not self.CALL_DATA: if not self.CALL_DATA:
print('({}) Recording transmission from subscriber: {}' .format(_network, int_id(_src_sub))) print('({}) Recording transmission from subscriber: {}' .format(self._system, int_id(_src_sub)))
self.CALL_DATA.append(_data) self.CALL_DATA.append(_data)
if _end: if _end:
self.CALL_DATA.append(_data) self.CALL_DATA.append(_data)
print('({}) Transmission ended, writing to disk: {}' .format(_network, filename)) print('({}) Transmission ended, writing to disk: {}' .format(self._system, filename))
pickle.dump(self.CALL_DATA, open(filename, 'wb')) pickle.dump(self.CALL_DATA, open(filename, 'wb'))
reactor.stop() reactor.stop()
print('Recording created, program terminating') print('Recording created, program terminating')
if __name__ == '__main__': if __name__ == '__main__':
import argparse
import os
import sys
import signal
import dmrlink_log
import dmrlink_config
# 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'
# Call the external routine to build the configuration dictionary
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
# Call the external routing to start the system logger
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...') logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
for ipsc_network in NETWORK:
if NETWORK[ipsc_network]['LOCAL']['ENABLED']: # Shut ourselves down gracefully with the IPSC peers.
networks[ipsc_network] = recordIPSC(ipsc_network) def sig_handler(_signal, _frame):
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP']) logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
for system in systems:
this_ipsc = systems[system]
logger.info('De-Registering from IPSC %s', system)
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
this_ipsc.send_to_ipsc(de_reg_req_pkt)
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
signal.signal(sig, sig_handler)
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = recordIPSC(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
reactor.run() reactor.run()

File diff suppressed because it is too large Load Diff

View File

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