Merge branch 'modularization'
This commit is contained in:
		
						commit
						8dbba4b42d
					
				@ -12,7 +12,7 @@ gateway = 127.0.0.1				# IP address of DMRGateway app
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
txTg = 9					# TG to use for all frames received from DMRGateway -> IPSC
 | 
			
		||||
txTs = 2					# Slot to use for frames received from DMRGateway -> IPSC
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										133
									
								
								ambe_audio.py
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								ambe_audio.py
									
									
									
									
									
								
							@ -28,7 +28,10 @@ from bitstring import BitArray
 | 
			
		||||
 | 
			
		||||
import sys, socket, ConfigParser, thread, traceback
 | 
			
		||||
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
 | 
			
		||||
import csv
 | 
			
		||||
import struct
 | 
			
		||||
@ -90,8 +93,8 @@ class ambeIPSC(IPSC):
 | 
			
		||||
    #_d = None
 | 
			
		||||
    ###### DEBUGDEBUGDEBUG
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, _name, _config, _logger):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        self.CALL_DATA = []
 | 
			
		||||
        
 | 
			
		||||
        #
 | 
			
		||||
@ -99,7 +102,7 @@ class ambeIPSC(IPSC):
 | 
			
		||||
        #
 | 
			
		||||
 | 
			
		||||
        self._currentTG = self._no_tg
 | 
			
		||||
        self._currentNetwork = str(args[0])
 | 
			
		||||
        self._currentNetwork = str(_name)
 | 
			
		||||
        self.readConfigFile(self._configFile, None, self._currentNetwork)
 | 
			
		||||
    
 | 
			
		||||
        logger.info('DMRLink ambe server')
 | 
			
		||||
@ -121,7 +124,7 @@ class ambeIPSC(IPSC):
 | 
			
		||||
    
 | 
			
		||||
        try:
 | 
			
		||||
            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:
 | 
			
		||||
            traceback.print_exc()
 | 
			
		||||
            logger.error( "Error: unable to start thread" )
 | 
			
		||||
@ -178,7 +181,7 @@ class ambeIPSC(IPSC):
 | 
			
		||||
            traceback.print_exc()
 | 
			
		||||
            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
 | 
			
		||||
        _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
 | 
			
		||||
 | 
			
		||||
    # 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.bind(('', self._ambeRxPort))      # Bind to the port
 | 
			
		||||
 | 
			
		||||
@ -274,16 +277,16 @@ class ambeIPSC(IPSC):
 | 
			
		||||
            s.listen(5)                     # Now wait for client connection.
 | 
			
		||||
            _sock, addr = s.accept()        # Establish connection with client.
 | 
			
		||||
            if int_id(self._tx_tg) > 0:     # Test if we are allowed to transmit
 | 
			
		||||
                self.playbackFromUDP(_sock, _network)
 | 
			
		||||
                self.playbackFromUDP(_sock, self._system)
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
    # 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
 | 
			
		||||
        _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)) )
 | 
			
		||||
        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)
 | 
			
		||||
 | 
			
		||||
            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.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
 | 
			
		||||
                self.rewriteFrame(_tempHead[i], self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
 | 
			
		||||
                #self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
 | 
			
		||||
                sleep(_delay)
 | 
			
		||||
 | 
			
		||||
            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
 | 
			
		||||
                    _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.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame)
 | 
			
		||||
                    self.rewriteFrame(_frame, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
 | 
			
		||||
                    #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
 | 
			
		||||
                else:
 | 
			
		||||
                    _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.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
 | 
			
		||||
            self.rewriteFrame(_tempTerm, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
 | 
			
		||||
            #self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
 | 
			
		||||
 | 
			
		||||
        except IOError:
 | 
			
		||||
            logger.error('Can not transmit to peers')
 | 
			
		||||
        logger.info('Transmit complete')
 | 
			
		||||
 | 
			
		||||
    def transmitDisabled(self, _sock, _network):
 | 
			
		||||
    def transmitDisabled(self, _sock):
 | 
			
		||||
        _eof = False
 | 
			
		||||
        logger.debug('Transmit disabled begin')
 | 
			
		||||
        while(_eof == False):
 | 
			
		||||
@ -386,7 +389,7 @@ 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)
 | 
			
		||||
        
 | 
			
		||||
@ -399,7 +402,6 @@ class ambeIPSC(IPSC):
 | 
			
		||||
        _ambe_frame3 = _ambe_frames[100:149]
 | 
			
		||||
        
 | 
			
		||||
        _tg_id = int_id(_dst_sub)
 | 
			
		||||
        _ts = 2 if _ts else 1
 | 
			
		||||
        
 | 
			
		||||
        self._busy_slots[_ts] = time()
 | 
			
		||||
        
 | 
			
		||||
@ -409,12 +411,12 @@ class ambeIPSC(IPSC):
 | 
			
		||||
#            self._d.write(struct.pack("i", __iLen))
 | 
			
		||||
#            self._d.write(_data)
 | 
			
		||||
#        else:
 | 
			
		||||
#            self.rewriteFrame(_data, _network, 1, 9)
 | 
			
		||||
#            self.rewriteFrame(_data, self._system, 1, 9)
 | 
			
		||||
        ###### DEBUGDEBUGDEBUG
 | 
			
		||||
       
 | 
			
		||||
        
 | 
			
		||||
        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 self._currentTG == self._no_tg:
 | 
			
		||||
                    _src_sub    = get_subscriber_info(_src_sub)
 | 
			
		||||
@ -459,7 +461,7 @@ class ambeIPSC(IPSC):
 | 
			
		||||
    
 | 
			
		||||
        else:
 | 
			
		||||
            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))
 | 
			
		||||
 | 
			
		||||
    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_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')
 | 
			
		||||
#        __iLen = len(_data)
 | 
			
		||||
#        self._d.write(struct.pack("i", __iLen))
 | 
			
		||||
@ -528,7 +530,7 @@ class ambeIPSC(IPSC):
 | 
			
		||||
                    print('New gateway_dmr_id = ' + str(self._gateway_dmr_id))
 | 
			
		||||
                elif _cmd == 'gateway_peer_id':
 | 
			
		||||
                    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))
 | 
			
		||||
                elif _cmd == 'restart':
 | 
			
		||||
                    reactor.callFromThread(reactor.stop)
 | 
			
		||||
@ -540,9 +542,9 @@ class ambeIPSC(IPSC):
 | 
			
		||||
                    logger.info( 'New TGs={}'.format(self._tg_filter) )
 | 
			
		||||
                elif _cmd == 'dump_template':
 | 
			
		||||
                    self.dumpTemplate('PrivateVoice.bin')
 | 
			
		||||
                elif _cmd == 'get_info':
 | 
			
		||||
                elif _cmd == 'get_alias':
 | 
			
		||||
                    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,
 | 
			
		||||
                                                                          get_subscriber_info(hex_str_3(self._gateway_dmr_id))), (self._dmrgui, 34003))
 | 
			
		||||
                elif _cmd == 'eval':
 | 
			
		||||
@ -615,10 +617,79 @@ class ambeIPSC(IPSC):
 | 
			
		||||
            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))
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
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...')
 | 
			
		||||
    for ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = ambeIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    # ID ALIAS CREATION
 | 
			
		||||
    # Download
 | 
			
		||||
    if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
 | 
			
		||||
        # Try updating peer aliases file
 | 
			
		||||
        result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
 | 
			
		||||
        logger.info(result)
 | 
			
		||||
        # Try updating subscriber aliases file
 | 
			
		||||
        result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
 | 
			
		||||
        logger.info(result)
 | 
			
		||||
        
 | 
			
		||||
    # Make Dictionaries
 | 
			
		||||
    peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
 | 
			
		||||
    if peer_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
 | 
			
		||||
        
 | 
			
		||||
    subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
 | 
			
		||||
    if subscriber_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
 | 
			
		||||
    
 | 
			
		||||
    talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
 | 
			
		||||
    if talkgroup_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
 | 
			
		||||
    
 | 
			
		||||
    # Shut ourselves down gracefully with the IPSC peers.
 | 
			
		||||
    def sig_handler(_signal, _frame):
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										238
									
								
								bridge.py
									
									
									
									
									
								
							
							
						
						
									
										238
									
								
								bridge.py
									
									
									
									
									
								
							@ -18,18 +18,18 @@
 | 
			
		||||
#   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 | 
			
		||||
###############################################################################
 | 
			
		||||
 | 
			
		||||
# This is a sample application to bridge traffic between IPSC networks. it uses
 | 
			
		||||
# This is a sample application to bridge traffic between IPSC systems. it uses
 | 
			
		||||
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
 | 
			
		||||
# configuration files. Both files have their own documentation for use.
 | 
			
		||||
#
 | 
			
		||||
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
 | 
			
		||||
# determine which voice calls are bridged between IPSC networks and which are
 | 
			
		||||
# determine which voice calls are bridged between IPSC systems and which are
 | 
			
		||||
# not.
 | 
			
		||||
#
 | 
			
		||||
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
 | 
			
		||||
# used when you want bridge.py to be "polite" or serve as a backup bridge. If
 | 
			
		||||
# a known bridge exists in either a source OR target IPSC network, then no
 | 
			
		||||
# bridging between those IPSC networks will take place. This behavior is
 | 
			
		||||
# bridging between those IPSC systems will take place. This behavior is
 | 
			
		||||
# dynamic and updates each keep-alive interval (main configuration file).
 | 
			
		||||
# For faster failover, configure a short keep-alive time and a low number of
 | 
			
		||||
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
 | 
			
		||||
@ -46,14 +46,17 @@
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from twisted.internet import reactor
 | 
			
		||||
from twisted.internet import task
 | 
			
		||||
from binascii import b2a_hex as h
 | 
			
		||||
from binascii import b2a_hex as ahex
 | 
			
		||||
from time import time
 | 
			
		||||
 | 
			
		||||
# For debugging
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
from importlib import import_module
 | 
			
		||||
 | 
			
		||||
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'
 | 
			
		||||
__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'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Constants for this application
 | 
			
		||||
#
 | 
			
		||||
BURST_DATA_TYPE = {
 | 
			
		||||
    'VOICE_HEAD':  '\x01',
 | 
			
		||||
    'VOICE_TERM':  '\x02',
 | 
			
		||||
    'SLOT1_VOICE': '\x0A',
 | 
			
		||||
    'SLOT2_VOICE': '\x8A'   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Minimum time between different subscribers transmitting on the same TGID
 | 
			
		||||
#
 | 
			
		||||
TS_CLEAR_TIME = .2
 | 
			
		||||
@ -81,8 +75,9 @@ TS_CLEAR_TIME = .2
 | 
			
		||||
# configuration file and listed as "active". It can be empty, 
 | 
			
		||||
# but it has to exist.
 | 
			
		||||
#
 | 
			
		||||
def build_rules(_bridge_rules):
 | 
			
		||||
    try:
 | 
			
		||||
    from bridge_rules import RULES as RULES_FILE
 | 
			
		||||
        rule_file = import_module(_bridge_rules)
 | 
			
		||||
        logger.info('Bridge rules file found and rules imported')
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        sys.exit('Bridging rules file not found or invalid')
 | 
			
		||||
@ -91,8 +86,8 @@ except ImportError:
 | 
			
		||||
    # we need to send in the actual data packets.
 | 
			
		||||
    #
 | 
			
		||||
 | 
			
		||||
for _ipsc in RULES_FILE:
 | 
			
		||||
    for _rule in RULES_FILE[_ipsc]['GROUP_VOICE']:
 | 
			
		||||
    for _ipsc in rule_file.RULES:
 | 
			
		||||
        for _rule in rule_file.RULES[_ipsc]['GROUP_VOICE']:
 | 
			
		||||
            _rule['SRC_GROUP']  = hex_str_3(_rule['SRC_GROUP'])
 | 
			
		||||
            _rule['DST_GROUP']  = hex_str_3(_rule['DST_GROUP'])
 | 
			
		||||
            _rule['SRC_TS']     = _rule['SRC_TS']
 | 
			
		||||
@ -103,48 +98,52 @@ for _ipsc in RULES_FILE:
 | 
			
		||||
                _rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
 | 
			
		||||
            _rule['TIMEOUT']= _rule['TIMEOUT']*60
 | 
			
		||||
            _rule['TIMER']      = time() + _rule['TIMEOUT']
 | 
			
		||||
    if _ipsc not in NETWORK:
 | 
			
		||||
        if _ipsc not in CONFIG['SYSTEMS']:
 | 
			
		||||
            sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
 | 
			
		||||
for _ipsc in NETWORK:
 | 
			
		||||
    if _ipsc not in RULES_FILE:
 | 
			
		||||
    for _ipsc in CONFIG['SYSTEMS']:
 | 
			
		||||
        if _ipsc not in rule_file.RULES:
 | 
			
		||||
            sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
 | 
			
		||||
 | 
			
		||||
RULES = RULES_FILE
 | 
			
		||||
    return rule_file.RULES
 | 
			
		||||
 | 
			
		||||
# Import List of Bridges
 | 
			
		||||
# 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
 | 
			
		||||
#
 | 
			
		||||
def build_bridges(_known_bridges):
 | 
			
		||||
    try:
 | 
			
		||||
    from known_bridges import BRIDGES
 | 
			
		||||
        bridges_file = import_module(_known_bridges)
 | 
			
		||||
        logger.info('Known bridges file found and bridge ID list imported ')
 | 
			
		||||
        return bridges_file.BRIDGES
 | 
			
		||||
    except ImportError:
 | 
			
		||||
        logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
 | 
			
		||||
    BRIDGES = []
 | 
			
		||||
        return []
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
# Import subscriber ACL
 | 
			
		||||
# ACL may be a single list of subscriber IDs
 | 
			
		||||
# Global action is to allow or deny them. Multiple lists with different actions and ranges
 | 
			
		||||
# are not yet implemented.
 | 
			
		||||
def build_acl(_sub_acl):
 | 
			
		||||
    try:
 | 
			
		||||
    from sub_acl import ACL_ACTION, ACL
 | 
			
		||||
    # uses more memory to build hex strings, but processes MUCH faster when checking for matches
 | 
			
		||||
    for i, e in enumerate(ACL):
 | 
			
		||||
        ACL[i] = hex_str_3(ACL[i])
 | 
			
		||||
    logger.info('Subscriber access control file found, subscriber ACL imported')
 | 
			
		||||
        acl_file = import_module(_sub_acl)
 | 
			
		||||
        for i, e in enumerate(acl_file.ACL):
 | 
			
		||||
            acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
 | 
			
		||||
        logger.info('ACL file found and ACL entries imported')
 | 
			
		||||
    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'
 | 
			
		||||
 | 
			
		||||
    # 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
 | 
			
		||||
if ACL_ACTION == 'PERMIT':
 | 
			
		||||
    global allow_sub
 | 
			
		||||
    if acl_file.ACL_ACTION == 'PERMIT':
 | 
			
		||||
        def allow_sub(_sub):
 | 
			
		||||
            if _sub in ACL:
 | 
			
		||||
                return True
 | 
			
		||||
            else:
 | 
			
		||||
                return False
 | 
			
		||||
elif ACL_ACTION == 'DENY':
 | 
			
		||||
    elif acl_file.ACL_ACTION == 'DENY':
 | 
			
		||||
        def allow_sub(_sub):
 | 
			
		||||
            if _sub not in ACL:
 | 
			
		||||
                return True
 | 
			
		||||
@ -154,6 +153,9 @@ else:
 | 
			
		||||
        def allow_sub(_sub):
 | 
			
		||||
            return True
 | 
			
		||||
    
 | 
			
		||||
    return acl_file.ACL
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
# Run this every minute for rule timer updates
 | 
			
		||||
def rule_timer_loop():
 | 
			
		||||
    logger.debug('(ALL IPSC) Rule timer loop started')
 | 
			
		||||
@ -179,15 +181,17 @@ def rule_timer_loop():
 | 
			
		||||
            else:
 | 
			
		||||
                logger.debug('Rule timer loop made no rule changes')
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
class bridgeIPSC(IPSC):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
        if BRIDGES:
 | 
			
		||||
            logger.info('Initializing backup/polite bridging')
 | 
			
		||||
    def __init__(self, _name, _config, _logger, _bridges):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        self.BRIDGES = _bridges
 | 
			
		||||
        if self.BRIDGES:
 | 
			
		||||
            self._logger.info('(%s) Initializing backup/polite bridging', self._system)
 | 
			
		||||
            self.BRIDGE = False
 | 
			
		||||
        else:
 | 
			
		||||
            self.BRIDGE = True
 | 
			
		||||
            logger.info('Initializing standard bridging')
 | 
			
		||||
            self._logger.info('Initializing standard bridging')
 | 
			
		||||
 | 
			
		||||
        self.IPSC_STATUS = {
 | 
			
		||||
            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)
 | 
			
		||||
    
 | 
			
		||||
    if BRIDGES:
 | 
			
		||||
    
 | 
			
		||||
    def startProtocol(self):
 | 
			
		||||
        IPSC.startProtocol(self)
 | 
			
		||||
 | 
			
		||||
        if self.BRIDGES:
 | 
			
		||||
            self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
 | 
			
		||||
            self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
 | 
			
		||||
 | 
			
		||||
    # This is the backup/polite bridge maintenance loop
 | 
			
		||||
    def bridge_presence_loop(self):
 | 
			
		||||
        self._logger.debug('(%s) Bridge presence loop initiated', self._system)
 | 
			
		||||
        _temp_bridge = True
 | 
			
		||||
        for peer in BRIDGES:
 | 
			
		||||
        for peer in self.BRIDGES:
 | 
			
		||||
            _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']):
 | 
			
		||||
                _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'] \
 | 
			
		||||
                and self._master['STATUS']['CONNECTED'] \
 | 
			
		||||
                and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
 | 
			
		||||
                _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:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
@ -231,32 +236,31 @@ class bridgeIPSC(IPSC):
 | 
			
		||||
    #     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
 | 
			
		||||
        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
 | 
			
		||||
        
 | 
			
		||||
        # 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)
 | 
			
		||||
        _seq_id = _data[5]
 | 
			
		||||
        _ts += 1
 | 
			
		||||
        
 | 
			
		||||
        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
 | 
			
		||||
            _status = networks[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
 | 
			
		||||
            _status = systems[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
 | 
			
		||||
            
 | 
			
		||||
            # This is the primary rule match to determine if the call will be routed.
 | 
			
		||||
            if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or networks[_target].BRIDGE == True):
 | 
			
		||||
            if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or systems[_target].BRIDGE == True):
 | 
			
		||||
                
 | 
			
		||||
                #
 | 
			
		||||
                # BEGIN CONTENTION HANDLING
 | 
			
		||||
                # 
 | 
			
		||||
                # 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:
 | 
			
		||||
                    #   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
 | 
			
		||||
                    # 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']:
 | 
			
		||||
                            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    
 | 
			
		||||
                    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']:
 | 
			
		||||
                            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
 | 
			
		||||
                    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']:
 | 
			
		||||
                            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
 | 
			
		||||
                    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']:
 | 
			
		||||
                            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
 | 
			
		||||
                #
 | 
			
		||||
                # END CONTENTION HANDLING
 | 
			
		||||
                #
 | 
			
		||||
                
 | 
			
		||||
                
 | 
			
		||||
                #
 | 
			
		||||
                # BEGIN FRAME FORWARDING
 | 
			
		||||
                #     
 | 
			
		||||
@ -293,7 +296,7 @@ class bridgeIPSC(IPSC):
 | 
			
		||||
                _tmp_data = _data
 | 
			
		||||
                
 | 
			
		||||
                # Re-Write the IPSC SRC to match the target network's ID
 | 
			
		||||
                _tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
 | 
			
		||||
                _tmp_data = _tmp_data.replace(_peerid, self._CONFIG['SYSTEMS'][_target]['LOCAL']['RADIO_ID'])
 | 
			
		||||
                
 | 
			
		||||
                # Re-Write the destination Group ID
 | 
			
		||||
                _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:]
 | 
			
		||||
 | 
			
		||||
                # 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
 | 
			
		||||
                #
 | 
			
		||||
@ -350,102 +353,155 @@ class bridgeIPSC(IPSC):
 | 
			
		||||
            if self.last_seq_id != _seq_id:
 | 
			
		||||
                self.last_seq_id = _seq_id
 | 
			
		||||
                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
 | 
			
		||||
        if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
 | 
			
		||||
            if self.last_seq_id == _seq_id:
 | 
			
		||||
                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:
 | 
			
		||||
                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
 | 
			
		||||
            for rule in RULES[_network]['GROUP_VOICE']:
 | 
			
		||||
            for rule in RULES[self._system]['GROUP_VOICE']:
 | 
			
		||||
                _target = rule['DST_NET']
 | 
			
		||||
                
 | 
			
		||||
                # 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)):
 | 
			
		||||
                    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.
 | 
			
		||||
                    for target_rule in RULES[_target]['GROUP_VOICE']:
 | 
			
		||||
                        if target_rule['NAME'] == rule['NAME']:
 | 
			
		||||
                            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
 | 
			
		||||
                if _dst_group in rule['ON']:
 | 
			
		||||
                    # Set the matching rule as ACTIVE
 | 
			
		||||
                    rule['ACTIVE'] = True
 | 
			
		||||
                    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
 | 
			
		||||
                    for target_rule in RULES[_target]['GROUP_VOICE']:
 | 
			
		||||
                        if target_rule['NAME'] == rule['NAME']:
 | 
			
		||||
                            target_rule['ACTIVE'] = True
 | 
			
		||||
                            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
 | 
			
		||||
                if _dst_group in rule['OFF']:
 | 
			
		||||
                    # Set the matching rule as ACTIVE
 | 
			
		||||
                    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
 | 
			
		||||
                    _target = rule['DST_NET']
 | 
			
		||||
                    for target_rule in RULES[_target]['GROUP_VOICE']:
 | 
			
		||||
                        if target_rule['NAME'] == rule['NAME']:
 | 
			
		||||
                            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
 | 
			
		||||
        #
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def group_data(self, _network, _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))
 | 
			
		||||
    def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        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
 | 
			
		||||
                # 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
 | 
			
		||||
                networks[target].send_to_ipsc(_tmp_data)
 | 
			
		||||
                systems[target].send_to_ipsc(_tmp_data)
 | 
			
		||||
 | 
			
		||||
    def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
 | 
			
		||||
    def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        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
 | 
			
		||||
                # 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
 | 
			
		||||
                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)
 | 
			
		||||
 | 
			
		||||
    logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
 | 
			
		||||
    
 | 
			
		||||
    # Shut ourselves down gracefully with the IPSC peers.
 | 
			
		||||
    def sig_handler(_signal, _frame):
 | 
			
		||||
        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)
 | 
			
		||||
    
 | 
			
		||||
    # 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 ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = bridgeIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    for system in CONFIG['SYSTEMS']:
 | 
			
		||||
        if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
 | 
			
		||||
            systems[system] = bridgeIPSC(system, CONFIG, logger, BRIDGES)
 | 
			
		||||
            reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
 | 
			
		||||
  
 | 
			
		||||
    # INITIALIZE THE REPORTING LOOP IF CONFIGURED
 | 
			
		||||
    if REPORTS['REPORT_NETWORKS']:
 | 
			
		||||
        reporting = task.LoopingCall(reporting_loop)
 | 
			
		||||
        reporting.start(REPORTS['REPORT_INTERVAL'])
 | 
			
		||||
    if CONFIG['REPORTS']['REPORT_NETWORKS']:
 | 
			
		||||
        reporting_loop = config_reports(CONFIG)
 | 
			
		||||
        reporting = task.LoopingCall(reporting_loop, logger)
 | 
			
		||||
        reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
 | 
			
		||||
        
 | 
			
		||||
    # INITIALIZE THE REPORTING LOOP IF CONFIGURED
 | 
			
		||||
    rule_timer = task.LoopingCall(rule_timer_loop)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										897
									
								
								dmrlink.py
									
									
									
									
									
								
							
							
						
						
									
										897
									
								
								dmrlink.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -64,6 +64,23 @@ LOG_LEVEL: INFO
 | 
			
		||||
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
 | 
			
		||||
# Please read these closely - catastrophic results could result by setting
 | 
			
		||||
# certain flags for things DMRlink cannot do.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										216
									
								
								dmrlink_config.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										216
									
								
								dmrlink_config.py
									
									
									
									
									
										Executable 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
									
								
							
							
						
						
									
										87
									
								
								dmrlink_log.py
									
									
									
									
									
										Executable 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'])
 | 
			
		||||
@ -45,6 +45,14 @@ IPSC_VER_22           = '\x04'
 | 
			
		||||
# Link Type Values - assumed that cap+, etc. are different, this is all I can confirm
 | 
			
		||||
LINK_TYPE_IPSC        = '\x04'
 | 
			
		||||
 | 
			
		||||
# Burst Data Types
 | 
			
		||||
BURST_DATA_TYPE = {
 | 
			
		||||
    'VOICE_HEAD':  '\x01',
 | 
			
		||||
    'VOICE_TERM':  '\x02',
 | 
			
		||||
    'SLOT1_VOICE': '\x0A',
 | 
			
		||||
    'SLOT2_VOICE': '\x8A'   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
 | 
			
		||||
IPSC_VER              = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										145
									
								
								log.py
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								log.py
									
									
									
									
									
								
							@ -25,7 +25,8 @@ from twisted.internet import reactor
 | 
			
		||||
from binascii import b2a_hex as h
 | 
			
		||||
 | 
			
		||||
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'
 | 
			
		||||
__copyright__   = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
 | 
			
		||||
@ -36,22 +37,33 @@ __email__       = 'n0mjs@me.com'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class logIPSC(IPSC):
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, _name, _config, _logger):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        self.ACTIVE_CALLS = []
 | 
			
		||||
        
 | 
			
		||||
    #************************************************
 | 
			
		||||
    #     CALLBACK FUNCTIONS FOR USER PACKET TYPES
 | 
			
		||||
    #************************************************
 | 
			
		||||
    
 | 
			
		||||
    def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
 | 
			
		||||
    #    _log = logger.debug
 | 
			
		||||
    def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
 | 
			
		||||
        if (_ts not in self.ACTIVE_CALLS) or _end:
 | 
			
		||||
            _time       = time.strftime('%m/%d/%y %H:%M:%S')
 | 
			
		||||
            _dst_sub    = get_info(int_id(_dst_sub), talkgroup_ids)
 | 
			
		||||
            _peerid     = get_info(int_id(_peerid), peer_ids)
 | 
			
		||||
            _src_sub    = get_info(int_id(_src_sub), subscriber_ids)
 | 
			
		||||
            _dst_sub    = get_alias(_dst_sub, talkgroup_ids)
 | 
			
		||||
            _peerid     = get_alias(_peerid, peer_ids)
 | 
			
		||||
            _src_sub    = get_alias(_src_sub, subscriber_ids)
 | 
			
		||||
            if not _end:    self.ACTIVE_CALLS.append(_ts)
 | 
			
		||||
            if _end:        self.ACTIVE_CALLS.remove(_ts)
 | 
			
		||||
            if _end:    _end = 'END'
 | 
			
		||||
            else:       _end = 'START'
 | 
			
		||||
        
 | 
			
		||||
            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, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
 | 
			
		||||
        if (_ts not in self.ACTIVE_CALLS) or _end:
 | 
			
		||||
            _time       = time.strftime('%m/%d/%y %H:%M:%S')
 | 
			
		||||
            _dst_sub    = get_alias(_dst_sub, subscriber_ids)
 | 
			
		||||
            _peerid     = get_alias(_peerid, peer_ids)
 | 
			
		||||
            _src_sub    = get_alias(_src_sub, subscriber_ids)
 | 
			
		||||
            if not _end:    self.ACTIVE_CALLS.append(_ts)
 | 
			
		||||
            if _end:        self.ACTIVE_CALLS.remove(_ts)
 | 
			
		||||
        
 | 
			
		||||
@ -60,42 +72,93 @@ class logIPSC(IPSC):
 | 
			
		||||
            if _end:    _end = 'END'
 | 
			
		||||
            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 {} 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 private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
 | 
			
		||||
    #    _log = logger.debug    
 | 
			
		||||
        if (_ts not in self.ACTIVE_CALLS) or _end:
 | 
			
		||||
            _time       = time.strftime('%m/%d/%y %H:%M:%S')
 | 
			
		||||
            _dst_sub    = get_info(int_id(_dst_sub), subscriber_ids)
 | 
			
		||||
            _peerid     = get_info(int_id(_peerid), peer_ids)
 | 
			
		||||
            _src_sub    = get_info(int_id(_src_sub), subscriber_ids)
 | 
			
		||||
            if not _end:    self.ACTIVE_CALLS.append(_ts)
 | 
			
		||||
            if _end:        self.ACTIVE_CALLS.remove(_ts)
 | 
			
		||||
    def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        _dst_sub    = get_alias(_dst_sub, talkgroup_ids)
 | 
			
		||||
        _peerid     = get_alias(_peerid, peer_ids)
 | 
			
		||||
        _src_sub    = get_alias(_src_sub, subscriber_ids)
 | 
			
		||||
        print('({}) Group Data Packet Received From: {}' .format(self._system, _src_sub))
 | 
			
		||||
    
 | 
			
		||||
            if _ts:     _ts = 2
 | 
			
		||||
            else:       _ts = 1
 | 
			
		||||
            if _end:    _end = 'END'
 | 
			
		||||
            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))
 | 
			
		||||
    
 | 
			
		||||
    def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        _dst_sub    = get_info(int_id(_dst_sub), talkgroup_ids)
 | 
			
		||||
        _peerid     = get_info(int_id(_peerid), peer_ids)
 | 
			
		||||
        _src_sub    = get_info(int_id(_src_sub), subscriber_ids)
 | 
			
		||||
        print('({}) Group Data Packet Received From: {}' .format(_network, _src_sub))
 | 
			
		||||
    
 | 
			
		||||
    def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        _dst_sub    = get_info(int_id(_dst_sub), subscriber_ids)
 | 
			
		||||
        _peerid     = get_info(int_id(_peerid), peer_ids)
 | 
			
		||||
        _src_sub    = get_info(int_id(_src_sub), subscriber_ids)
 | 
			
		||||
        print('({}) Private Data Packet Received From: {} To: {}' .format(_network, _src_sub, _dst_sub))
 | 
			
		||||
    def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):    
 | 
			
		||||
        _dst_sub    = get_alias(_dst_sub, subscriber_ids)
 | 
			
		||||
        _peerid     = get_alias(_peerid, peer_ids)
 | 
			
		||||
        _src_sub    = get_alias(_src_sub, subscriber_ids)
 | 
			
		||||
        print('({}) Private Data Packet Received From: {} To: {}' .format(self._system, _src_sub, _dst_sub))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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...')
 | 
			
		||||
    for ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = logIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    # ID ALIAS CREATION
 | 
			
		||||
    # Download
 | 
			
		||||
    if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
 | 
			
		||||
        # Try updating peer aliases file
 | 
			
		||||
        result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
 | 
			
		||||
        logger.info(result)
 | 
			
		||||
        # Try updating subscriber aliases file
 | 
			
		||||
        result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
 | 
			
		||||
        logger.info(result)
 | 
			
		||||
        
 | 
			
		||||
    # Make Dictionaries
 | 
			
		||||
    peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
 | 
			
		||||
    if peer_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
 | 
			
		||||
        
 | 
			
		||||
    subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
 | 
			
		||||
    if subscriber_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
 | 
			
		||||
    
 | 
			
		||||
    talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
 | 
			
		||||
    if talkgroup_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
 | 
			
		||||
    
 | 
			
		||||
    # Shut ourselves down gracefully with the IPSC peers.
 | 
			
		||||
    def sig_handler(_signal, _frame):
 | 
			
		||||
        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()
 | 
			
		||||
							
								
								
									
										4719
									
								
								peer_ids.csv
									
									
									
									
									
								
							
							
						
						
									
										4719
									
								
								peer_ids.csv
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -32,11 +32,14 @@
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from twisted.internet import reactor
 | 
			
		||||
from binascii import b2a_hex as h
 | 
			
		||||
 | 
			
		||||
import sys, time
 | 
			
		||||
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'
 | 
			
		||||
__copyright__   = 'Copyright (c) 2014 - 2015 Cortney T. Buffington, N0MJS and the K0USY Group'
 | 
			
		||||
@ -45,14 +48,6 @@ __license__     = 'GNU GPLv3'
 | 
			
		||||
__maintainer__  = 'Cort Buffington, N0MJS'
 | 
			
		||||
__email__       = 'n0mjs@me.com'
 | 
			
		||||
 | 
			
		||||
# Constants for this application
 | 
			
		||||
#
 | 
			
		||||
BURST_DATA_TYPE = {
 | 
			
		||||
    'VOICE_HEAD':  '\x01',
 | 
			
		||||
    'VOICE_TERM':  '\x02',
 | 
			
		||||
    'SLOT1_VOICE': '\x0A',
 | 
			
		||||
    'SLOT2_VOICE': '\x8A'   
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# path+filename for the transmission to play back
 | 
			
		||||
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',]
 | 
			
		||||
 | 
			
		||||
class playIPSC(IPSC):
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, _name, _config, _logger):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        self.CALL_DATA = []
 | 
			
		||||
        self.event_id = 1
 | 
			
		||||
        
 | 
			
		||||
@ -76,30 +70,30 @@ class playIPSC(IPSC):
 | 
			
		||||
    #     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:
 | 
			
		||||
            _self_peer = NETWORK[_network]['LOCAL']['RADIO_ID']
 | 
			
		||||
            _self_peer = self._config['LOCAL']['RADIO_ID']
 | 
			
		||||
            _self_src = _self_peer[1:]
 | 
			
		||||
            
 | 
			
		||||
            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
 | 
			
		||||
            
 | 
			
		||||
            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
 | 
			
		||||
            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
 | 
			
		||||
            
 | 
			
		||||
            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)
 | 
			
		||||
            _burst_data_type = _data[30]
 | 
			
		||||
                
 | 
			
		||||
            time.sleep(2)
 | 
			
		||||
            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:
 | 
			
		||||
                _tmp_data = i
 | 
			
		||||
@ -113,9 +107,9 @@ class playIPSC(IPSC):
 | 
			
		||||
                
 | 
			
		||||
                # Re-Write IPSC timeslot value
 | 
			
		||||
                _call_info = int_id(_tmp_data[17:18])
 | 
			
		||||
                if _ts == 0:
 | 
			
		||||
                if _ts == 1:
 | 
			
		||||
                    _call_info &= ~(1 << 5)
 | 
			
		||||
                elif _ts == 1:
 | 
			
		||||
                elif _ts == 2:
 | 
			
		||||
                    _call_info |= 1 << 5
 | 
			
		||||
                _call_info = chr(_call_info)
 | 
			
		||||
                _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
 | 
			
		||||
                if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']:
 | 
			
		||||
                    # Re-Write timeslot if necessary...
 | 
			
		||||
                    if _ts == 0:
 | 
			
		||||
                    if _ts == 1:
 | 
			
		||||
                        _burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE']
 | 
			
		||||
                    elif _ts == 1:
 | 
			
		||||
                    elif _ts == 2:
 | 
			
		||||
                        _burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE']
 | 
			
		||||
                    _tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
 | 
			
		||||
 | 
			
		||||
@ -134,13 +128,58 @@ class playIPSC(IPSC):
 | 
			
		||||
                self.send_to_ipsc(_tmp_data)
 | 
			
		||||
                time.sleep(0.06)
 | 
			
		||||
            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
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
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...')
 | 
			
		||||
    for ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = playIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    # 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] = playIPSC(system, CONFIG, logger)
 | 
			
		||||
            reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    reactor.run()
 | 
			
		||||
							
								
								
									
										98
									
								
								playback.py
									
									
									
									
									
								
							
							
						
						
									
										98
									
								
								playback.py
									
									
									
									
									
								
							@ -22,10 +22,11 @@
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
from twisted.internet import reactor
 | 
			
		||||
from binascii import b2a_hex as h
 | 
			
		||||
from binascii import b2a_hex as ahex
 | 
			
		||||
 | 
			
		||||
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'
 | 
			
		||||
__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_SUB     = hex_str_3(SUB)
 | 
			
		||||
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):
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, _name, _config, _logger):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        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
 | 
			
		||||
    #************************************************
 | 
			
		||||
    #
 | 
			
		||||
    if GROUP_REPEAT:
 | 
			
		||||
	logger.info('Playback: DEFINING GROUP REPEAT FUNCTION')
 | 
			
		||||
        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 HEX_TGID == _dst_sub and _ts in GROUP_TS:
 | 
			
		||||
                if not _end:
 | 
			
		||||
                    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 = 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)
 | 
			
		||||
                if _end:
 | 
			
		||||
                    self.CALL_DATA.append(_data)
 | 
			
		||||
                    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:
 | 
			
		||||
                        _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:
 | 
			
		||||
                            _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
 | 
			
		||||
                        self.send_to_ipsc(_tmp_data)
 | 
			
		||||
                        time.sleep(0.06)
 | 
			
		||||
                    self.CALL_DATA = []
 | 
			
		||||
                
 | 
			
		||||
    if PRIVATE_REPEAT:
 | 
			
		||||
	logger.info('Playback: DEFINING PRIVATE REPEAT FUNCTION')
 | 
			
		||||
        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 HEX_SUB == _dst_sub and _ts in PRIVATE_TS:
 | 
			
		||||
                if not _end:
 | 
			
		||||
                    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
 | 
			
		||||
                    self.CALL_DATA.append(_tmp_data)
 | 
			
		||||
                if _end:
 | 
			
		||||
                    self.CALL_DATA.append(_data)
 | 
			
		||||
                    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_dst = _dst_sub
 | 
			
		||||
                    for i in self.CALL_DATA:
 | 
			
		||||
                        _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(_src_sub, _orig_dst)
 | 
			
		||||
                        _tmp_data = _tmp_data.replace(BOGUS_SUB, _orig_src)
 | 
			
		||||
@ -107,10 +112,55 @@ class playbackIPSC(IPSC):
 | 
			
		||||
                        time.sleep(0.06)
 | 
			
		||||
                    self.CALL_DATA = []
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
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...')
 | 
			
		||||
    for ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = playbackIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    # 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] = playbackIPSC(system, CONFIG, logger)
 | 
			
		||||
            reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    reactor.run()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										115
									
								
								rcm.py
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								rcm.py
									
									
									
									
									
								
							@ -26,12 +26,13 @@ from __future__ import print_function
 | 
			
		||||
from twisted.internet.protocol import DatagramProtocol
 | 
			
		||||
from twisted.internet import reactor
 | 
			
		||||
from twisted.internet import task
 | 
			
		||||
from binascii import b2a_hex as h
 | 
			
		||||
from binascii import b2a_hex as ahex
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import binascii
 | 
			
		||||
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'
 | 
			
		||||
__copyright__   = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
 | 
			
		||||
@ -51,15 +52,14 @@ rpt = True
 | 
			
		||||
nack = True
 | 
			
		||||
 | 
			
		||||
class rcmIPSC(IPSC):
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, _name, _config, _logger):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        
 | 
			
		||||
    #************************************************
 | 
			
		||||
    #     CALLBACK FUNCTIONS FOR USER PACKET TYPES
 | 
			
		||||
    #************************************************
 | 
			
		||||
    #
 | 
			
		||||
    def call_mon_status(self, _network, _data):
 | 
			
		||||
    def call_mon_status(self, _data):
 | 
			
		||||
        if not status:
 | 
			
		||||
            return
 | 
			
		||||
        _source =   _data[1:5]
 | 
			
		||||
@ -73,19 +73,19 @@ class rcmIPSC(IPSC):
 | 
			
		||||
        _prio =     _data[23]
 | 
			
		||||
        _sec =      _data[24]
 | 
			
		||||
        
 | 
			
		||||
        _source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids))
 | 
			
		||||
        _ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_info(int_id(_ipsc_src), peer_ids))
 | 
			
		||||
        _rf_src = str(int_id(_rf_src)) + ', ' + str(get_info(int_id(_rf_src), subscriber_ids))
 | 
			
		||||
        _source = str(int_id(_source)) + ', ' + str(get_alias(_source, 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_alias(_rf_src, subscriber_ids))
 | 
			
		||||
        
 | 
			
		||||
        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:
 | 
			
		||||
            _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('TIME:        ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
 | 
			
		||||
        print('DATA SOURCE: ', _source)
 | 
			
		||||
        print('IPSC:        ', _network)
 | 
			
		||||
        print('IPSC:        ', self._system)
 | 
			
		||||
        print('IPSC Source: ', _ipsc_src)
 | 
			
		||||
        print('Timeslot:    ', TS[_ts])
 | 
			
		||||
        try:
 | 
			
		||||
@ -100,14 +100,14 @@ class rcmIPSC(IPSC):
 | 
			
		||||
        print('Target Sub:  ', _rf_tgt)
 | 
			
		||||
        print()
 | 
			
		||||
    
 | 
			
		||||
    def call_mon_rpt(self, _network, _data):
 | 
			
		||||
    def call_mon_rpt(self, _data):
 | 
			
		||||
        if not rpt:
 | 
			
		||||
            return
 | 
			
		||||
        _source    = _data[1:5]
 | 
			
		||||
        _ts1_state = _data[5]
 | 
			
		||||
        _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('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()
 | 
			
		||||
            
 | 
			
		||||
    def call_mon_nack(self, _network, _data):
 | 
			
		||||
    def call_mon_nack(self, _data):
 | 
			
		||||
        if not nack:
 | 
			
		||||
            return
 | 
			
		||||
        _source = _data[1:5]
 | 
			
		||||
        _nack =   _data[5]
 | 
			
		||||
        
 | 
			
		||||
        _source = get_info(int_id(_source), peer_ids)
 | 
			
		||||
        _source = get_alias(_source, peer_ids)
 | 
			
		||||
        
 | 
			
		||||
        print('Call Monitor - Transmission NACK')
 | 
			
		||||
        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()
 | 
			
		||||
    
 | 
			
		||||
    def repeater_wake_up(self, _network, _data):
 | 
			
		||||
    def repeater_wake_up(self, _data):
 | 
			
		||||
        _source = _data[1:5]
 | 
			
		||||
        _source_dec = int_id(_source)
 | 
			
		||||
        _source_name = get_info(_source_dec, peer_ids)
 | 
			
		||||
        #print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(_network, _source_name, _source_dec))
 | 
			
		||||
        _source_name = get_alias(_source, peer_ids)
 | 
			
		||||
        print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(self._system, _source_name, int_id(_source)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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...')
 | 
			
		||||
    for ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = rcmIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    # ID ALIAS CREATION
 | 
			
		||||
    # Download
 | 
			
		||||
    if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
 | 
			
		||||
        # Try updating peer aliases file
 | 
			
		||||
        result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
 | 
			
		||||
        logger.info(result)
 | 
			
		||||
        # Try updating subscriber aliases file
 | 
			
		||||
        result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
 | 
			
		||||
        logger.info(result)
 | 
			
		||||
        
 | 
			
		||||
    # Make Dictionaries
 | 
			
		||||
    peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
 | 
			
		||||
    if peer_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
 | 
			
		||||
        
 | 
			
		||||
    subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
 | 
			
		||||
    if subscriber_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
 | 
			
		||||
    
 | 
			
		||||
    talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
 | 
			
		||||
    if talkgroup_ids:
 | 
			
		||||
        logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
 | 
			
		||||
    
 | 
			
		||||
    # Shut ourselves down gracefully with the IPSC peers.
 | 
			
		||||
    def sig_handler(_signal, _frame):
 | 
			
		||||
        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()
 | 
			
		||||
							
								
								
									
										78
									
								
								record.py
									
									
									
									
									
								
							
							
						
						
									
										78
									
								
								record.py
									
									
									
									
									
								
							@ -27,7 +27,8 @@ from binascii import b2a_hex as h
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
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'
 | 
			
		||||
__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\')? ')
 | 
			
		||||
    if ts == '1' or ts == '2' or ts =='both':
 | 
			
		||||
        if ts == '1':
 | 
			
		||||
            ts = (0,)
 | 
			
		||||
        if ts == '2':
 | 
			
		||||
            ts = (1,)
 | 
			
		||||
        if ts == '2':
 | 
			
		||||
            ts = (2,)
 | 
			
		||||
        if ts == 'both':
 | 
			
		||||
            ts = (0,1)
 | 
			
		||||
            ts = (1,2)
 | 
			
		||||
        break
 | 
			
		||||
    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? ')
 | 
			
		||||
 | 
			
		||||
class recordIPSC(IPSC):
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        IPSC.__init__(self, *args, **kwargs)
 | 
			
		||||
    def __init__(self, _name, _config, _logger):
 | 
			
		||||
        IPSC.__init__(self, _name, _config, _logger)
 | 
			
		||||
        self.CALL_DATA = []
 | 
			
		||||
        
 | 
			
		||||
    #************************************************
 | 
			
		||||
@ -75,39 +75,83 @@ class recordIPSC(IPSC):
 | 
			
		||||
    #
 | 
			
		||||
    if tx_type == 'g':
 | 
			
		||||
	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 not _end:
 | 
			
		||||
                    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)
 | 
			
		||||
                if _end:
 | 
			
		||||
                    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'))
 | 
			
		||||
                    reactor.stop()
 | 
			
		||||
                    print('Recording created, program terminating')
 | 
			
		||||
                
 | 
			
		||||
    if tx_type == 'p':
 | 
			
		||||
	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 not _end:
 | 
			
		||||
                    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)
 | 
			
		||||
                if _end:
 | 
			
		||||
                    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'))
 | 
			
		||||
                    reactor.stop()
 | 
			
		||||
                    print('Recording created, program terminating')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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...')
 | 
			
		||||
    for ipsc_network in NETWORK:
 | 
			
		||||
        if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
 | 
			
		||||
            networks[ipsc_network] = recordIPSC(ipsc_network)
 | 
			
		||||
            reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    # 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] = recordIPSC(system, CONFIG, logger)
 | 
			
		||||
            reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
 | 
			
		||||
    
 | 
			
		||||
    reactor.run()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										75733
									
								
								subscriber_ids.csv
									
									
									
									
									
								
							
							
						
						
									
										75733
									
								
								subscriber_ids.csv
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,17 +1 @@
 | 
			
		||||
Worldwide,1
 | 
			
		||||
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
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
 | 
			
		||||
		
		
			
  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user