Merge branch 'modularization'
This commit is contained in:
commit
8dbba4b42d
|
@ -12,8 +12,8 @@ gateway = 127.0.0.1 # IP address of DMRGateway app
|
||||||
toGatewayPort = 31000 # Port DMRGateway is listening on for AMBE frames to decode
|
toGatewayPort = 31000 # Port DMRGateway is listening on for AMBE frames to decode
|
||||||
remoteControlPort = 31002 # Port that ambe_audio is listening on for remote control commands
|
remoteControlPort = 31002 # Port that ambe_audio is listening on for remote control commands
|
||||||
fromGatewayPort = 31003 # Port to listen on for AMBE frames to transmit to all peers
|
fromGatewayPort = 31003 # Port to listen on for AMBE frames to transmit to all peers
|
||||||
gatewayDmrId = 0 # id to use when transmitting from the gateway
|
gatewayDmrId = 1312000 # id to use when transmitting from the gateway
|
||||||
tgFilter = 9 # A list of TG IDs to monitor. All TGs will be passed to DMRGateway
|
tgFilter = 9 # A list of TG IDs to monitor. All TGs will be passed to DMRGateway
|
||||||
txTg = 9 # TG to use for all frames received from DMRGateway -> IPSC
|
txTg = 9 # TG to use for all frames received from DMRGateway -> IPSC
|
||||||
txTs = 2 # Slot to use for frames received from DMRGateway -> IPSC
|
txTs = 2 # Slot to use for frames received from DMRGateway -> IPSC
|
||||||
#
|
#
|
||||||
|
|
135
ambe_audio.py
135
ambe_audio.py
|
@ -28,7 +28,10 @@ from bitstring import BitArray
|
||||||
|
|
||||||
import sys, socket, ConfigParser, thread, traceback
|
import sys, socket, ConfigParser, thread, traceback
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3, hex_str_4, get_info, talkgroup_ids, peer_ids, PATH, get_subscriber_info, reread_subscribers
|
|
||||||
|
from dmrlink import IPSC, systems
|
||||||
|
from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias
|
||||||
|
|
||||||
from time import time, sleep, clock, localtime, strftime
|
from time import time, sleep, clock, localtime, strftime
|
||||||
import csv
|
import csv
|
||||||
import struct
|
import struct
|
||||||
|
@ -90,8 +93,8 @@ class ambeIPSC(IPSC):
|
||||||
#_d = None
|
#_d = None
|
||||||
###### DEBUGDEBUGDEBUG
|
###### DEBUGDEBUGDEBUG
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, _name, _config, _logger):
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -99,7 +102,7 @@ class ambeIPSC(IPSC):
|
||||||
#
|
#
|
||||||
|
|
||||||
self._currentTG = self._no_tg
|
self._currentTG = self._no_tg
|
||||||
self._currentNetwork = str(args[0])
|
self._currentNetwork = str(_name)
|
||||||
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
||||||
|
|
||||||
logger.info('DMRLink ambe server')
|
logger.info('DMRLink ambe server')
|
||||||
|
@ -121,7 +124,7 @@ class ambeIPSC(IPSC):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
thread.start_new_thread( self.remote_control, (self._remote_control_port, ) ) # Listen for remote control commands
|
thread.start_new_thread( self.remote_control, (self._remote_control_port, ) ) # Listen for remote control commands
|
||||||
thread.start_new_thread( self.launchUDP, (args[0], ) ) # Package AMBE into IPSC frames and send to all peers
|
thread.start_new_thread( self.launchUDP, (_name, ) ) # Package AMBE into IPSC frames and send to all peers
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
logger.error( "Error: unable to start thread" )
|
logger.error( "Error: unable to start thread" )
|
||||||
|
@ -178,7 +181,7 @@ class ambeIPSC(IPSC):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
||||||
|
|
||||||
def rewriteFrame( self, _frame, _network, _newSlot, _newGroup, _newSouceID, _newPeerID ):
|
def rewriteFrame( self, _frame, _newSlot, _newGroup, _newSouceID, _newPeerID ):
|
||||||
|
|
||||||
_peerid = _frame[1:5] # int32 peer who is sending us a packet
|
_peerid = _frame[1:5] # int32 peer who is sending us a packet
|
||||||
_src_sub = _frame[6:9] # int32 Id of source
|
_src_sub = _frame[6:9] # int32 Id of source
|
||||||
|
@ -266,7 +269,7 @@ class ambeIPSC(IPSC):
|
||||||
return _ambeAll.tobytes() # Return the 49 * 3 as an array of bytes
|
return _ambeAll.tobytes() # Return the 49 * 3 as an array of bytes
|
||||||
|
|
||||||
# Set up the socket and run the method to gather the AMBE. Sending it to all peers
|
# Set up the socket and run the method to gather the AMBE. Sending it to all peers
|
||||||
def launchUDP(self, _network):
|
def launchUDP(self):
|
||||||
s = socket.socket() # Create a socket object
|
s = socket.socket() # Create a socket object
|
||||||
s.bind(('', self._ambeRxPort)) # Bind to the port
|
s.bind(('', self._ambeRxPort)) # Bind to the port
|
||||||
|
|
||||||
|
@ -274,16 +277,16 @@ class ambeIPSC(IPSC):
|
||||||
s.listen(5) # Now wait for client connection.
|
s.listen(5) # Now wait for client connection.
|
||||||
_sock, addr = s.accept() # Establish connection with client.
|
_sock, addr = s.accept() # Establish connection with client.
|
||||||
if int_id(self._tx_tg) > 0: # Test if we are allowed to transmit
|
if int_id(self._tx_tg) > 0: # Test if we are allowed to transmit
|
||||||
self.playbackFromUDP(_sock, _network)
|
self.playbackFromUDP(_sock, self._system)
|
||||||
else:
|
else:
|
||||||
self.transmitDisabled(_sock, _network) #tg is zero, so just eat the network trafic
|
self.transmitDisabled(_sock, self._system) #tg is zero, so just eat the network trafic
|
||||||
_sock.close()
|
_sock.close()
|
||||||
|
|
||||||
# This represents a full transmission (HEAD, VOICE and TERM)
|
# This represents a full transmission (HEAD, VOICE and TERM)
|
||||||
def playbackFromUDP(self, _sock, _network):
|
def playbackFromUDP(self, _sock):
|
||||||
_delay = 0.055 # Yes, I know it should be 0.06, but there seems to be some latency, so this is a hack
|
_delay = 0.055 # Yes, I know it should be 0.06, but there seems to be some latency, so this is a hack
|
||||||
_src_sub = hex_str_3(self._gateway_dmr_id) # DMR ID to sign this transmission with
|
_src_sub = hex_str_3(self._gateway_dmr_id) # DMR ID to sign this transmission with
|
||||||
_src_peer = NETWORK[_network]['LOCAL']['RADIO_ID'] # Use this peers ID as the source repeater
|
_src_peer = NETWORK[self._system]['LOCAL']['RADIO_ID'] # Use this peers ID as the source repeater
|
||||||
|
|
||||||
logger.info('Transmit from gateway to TG {}:'.format(int_id(self._tx_tg)) )
|
logger.info('Transmit from gateway to TG {}:'.format(int_id(self._tx_tg)) )
|
||||||
try:
|
try:
|
||||||
|
@ -310,8 +313,8 @@ class ambeIPSC(IPSC):
|
||||||
self._seq = randint(0,32767) # A transmission uses a random number to begin its sequence (16 bit)
|
self._seq = randint(0,32767) # A transmission uses a random number to begin its sequence (16 bit)
|
||||||
|
|
||||||
for i in range(0, 3): # Output the 3 HEAD frames to our peers
|
for i in range(0, 3): # Output the 3 HEAD frames to our peers
|
||||||
self.rewriteFrame(_tempHead[i], _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
self.rewriteFrame(_tempHead[i], self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||||
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
|
#self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
|
||||||
sleep(_delay)
|
sleep(_delay)
|
||||||
|
|
||||||
i = 0 # Initialize the VOICE template index
|
i = 0 # Initialize the VOICE template index
|
||||||
|
@ -321,21 +324,21 @@ class ambeIPSC(IPSC):
|
||||||
i = (i + 1) % 6 # Round robbin with the 6 VOICE templates
|
i = (i + 1) % 6 # Round robbin with the 6 VOICE templates
|
||||||
_frame = _tempVoice[i][:33] + _ambe + _tempVoice[i][52:] # Insert the 3 49 bit AMBE frames
|
_frame = _tempVoice[i][:33] + _ambe + _tempVoice[i][52:] # Insert the 3 49 bit AMBE frames
|
||||||
|
|
||||||
self.rewriteFrame(_frame, _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
self.rewriteFrame(_frame, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||||
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame)
|
#self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame)
|
||||||
|
|
||||||
sleep(_delay) # Since this comes from a file we have to add delay between IPSC frames
|
sleep(_delay) # Since this comes from a file we have to add delay between IPSC frames
|
||||||
else:
|
else:
|
||||||
_eof = True # There are no more AMBE frames, so terminate the loop
|
_eof = True # There are no more AMBE frames, so terminate the loop
|
||||||
|
|
||||||
self.rewriteFrame(_tempTerm, _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
self.rewriteFrame(_tempTerm, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||||
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
|
#self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
logger.error('Can not transmit to peers')
|
logger.error('Can not transmit to peers')
|
||||||
logger.info('Transmit complete')
|
logger.info('Transmit complete')
|
||||||
|
|
||||||
def transmitDisabled(self, _sock, _network):
|
def transmitDisabled(self, _sock):
|
||||||
_eof = False
|
_eof = False
|
||||||
logger.debug('Transmit disabled begin')
|
logger.debug('Transmit disabled begin')
|
||||||
while(_eof == False):
|
while(_eof == False):
|
||||||
|
@ -386,20 +389,19 @@ class ambeIPSC(IPSC):
|
||||||
#************************************************
|
#************************************************
|
||||||
#
|
#
|
||||||
|
|
||||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
|
|
||||||
#self.dumpIPSCFrame(_data)
|
#self.dumpIPSCFrame(_data)
|
||||||
|
|
||||||
# THIS FUNCTION IS NOT COMPLETE!!!!
|
# THIS FUNCTION IS NOT COMPLETE!!!!
|
||||||
_payload_type = _data[30:31]
|
_payload_type = _data[30:31]
|
||||||
# _ambe_frames = _data[33:52]
|
# _ambe_frames = _data[33:52]
|
||||||
_ambe_frames = BitArray('0x'+h(_data[33:52]))
|
_ambe_frames = BitArray('0x'+h(_data[33:52]))
|
||||||
_ambe_frame1 = _ambe_frames[0:49]
|
_ambe_frame1 = _ambe_frames[0:49]
|
||||||
_ambe_frame2 = _ambe_frames[50:99]
|
_ambe_frame2 = _ambe_frames[50:99]
|
||||||
_ambe_frame3 = _ambe_frames[100:149]
|
_ambe_frame3 = _ambe_frames[100:149]
|
||||||
|
|
||||||
_tg_id = int_id(_dst_sub)
|
_tg_id = int_id(_dst_sub)
|
||||||
_ts = 2 if _ts else 1
|
|
||||||
|
|
||||||
self._busy_slots[_ts] = time()
|
self._busy_slots[_ts] = time()
|
||||||
|
|
||||||
|
@ -409,12 +411,12 @@ class ambeIPSC(IPSC):
|
||||||
# self._d.write(struct.pack("i", __iLen))
|
# self._d.write(struct.pack("i", __iLen))
|
||||||
# self._d.write(_data)
|
# self._d.write(_data)
|
||||||
# else:
|
# else:
|
||||||
# self.rewriteFrame(_data, _network, 1, 9)
|
# self.rewriteFrame(_data, self._system, 1, 9)
|
||||||
###### DEBUGDEBUGDEBUG
|
###### DEBUGDEBUGDEBUG
|
||||||
|
|
||||||
|
|
||||||
if _tg_id in self._tg_filter: #All TGs
|
if _tg_id in self._tg_filter: #All TGs
|
||||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
_dst_sub = get_alias(_dst_sub, talkgroup_ids)
|
||||||
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
if self._currentTG == self._no_tg:
|
if self._currentTG == self._no_tg:
|
||||||
_src_sub = get_subscriber_info(_src_sub)
|
_src_sub = get_subscriber_info(_src_sub)
|
||||||
|
@ -459,7 +461,7 @@ class ambeIPSC(IPSC):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
_dst_sub = get_alias(_dst_sub, talkgroup_ids)
|
||||||
logger.warning('Ignored Voice Transmission Start on TS {} and TG {}'.format(_ts, _dst_sub))
|
logger.warning('Ignored Voice Transmission Start on TS {} and TG {}'.format(_ts, _dst_sub))
|
||||||
|
|
||||||
def outputFrames(self, _ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3):
|
def outputFrames(self, _ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3):
|
||||||
|
@ -479,7 +481,7 @@ class ambeIPSC(IPSC):
|
||||||
self._sock.sendto(_ambe_frame2.tobytes(), (self._gateway, self._gateway_port))
|
self._sock.sendto(_ambe_frame2.tobytes(), (self._gateway, self._gateway_port))
|
||||||
self._sock.sendto(_ambe_frame3.tobytes(), (self._gateway, self._gateway_port))
|
self._sock.sendto(_ambe_frame3.tobytes(), (self._gateway, self._gateway_port))
|
||||||
|
|
||||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
print('private voice')
|
print('private voice')
|
||||||
# __iLen = len(_data)
|
# __iLen = len(_data)
|
||||||
# self._d.write(struct.pack("i", __iLen))
|
# self._d.write(struct.pack("i", __iLen))
|
||||||
|
@ -528,7 +530,7 @@ class ambeIPSC(IPSC):
|
||||||
print('New gateway_dmr_id = ' + str(self._gateway_dmr_id))
|
print('New gateway_dmr_id = ' + str(self._gateway_dmr_id))
|
||||||
elif _cmd == 'gateway_peer_id':
|
elif _cmd == 'gateway_peer_id':
|
||||||
peerID = int(_tmp.split('=')[1])
|
peerID = int(_tmp.split('=')[1])
|
||||||
NETWORK[_network]['LOCAL']['RADIO_ID'] = hex_str_3(peerID)
|
self._config['LOCAL']['RADIO_ID'] = hex_str_3(peerID)
|
||||||
print('New peer_id = ' + str(peerID))
|
print('New peer_id = ' + str(peerID))
|
||||||
elif _cmd == 'restart':
|
elif _cmd == 'restart':
|
||||||
reactor.callFromThread(reactor.stop)
|
reactor.callFromThread(reactor.stop)
|
||||||
|
@ -540,9 +542,9 @@ class ambeIPSC(IPSC):
|
||||||
logger.info( 'New TGs={}'.format(self._tg_filter) )
|
logger.info( 'New TGs={}'.format(self._tg_filter) )
|
||||||
elif _cmd == 'dump_template':
|
elif _cmd == 'dump_template':
|
||||||
self.dumpTemplate('PrivateVoice.bin')
|
self.dumpTemplate('PrivateVoice.bin')
|
||||||
elif _cmd == 'get_info':
|
elif _cmd == 'get_alias':
|
||||||
self._sock.sendto('reply dmr_info {} {} {} {}'.format(self._currentNetwork,
|
self._sock.sendto('reply dmr_info {} {} {} {}'.format(self._currentNetwork,
|
||||||
int_id(NETWORK[self._currentNetwork]['LOCAL']['RADIO_ID']),
|
int_id(self._CONFIG[self._currentNetwork]['LOCAL']['RADIO_ID']),
|
||||||
self._gateway_dmr_id,
|
self._gateway_dmr_id,
|
||||||
get_subscriber_info(hex_str_3(self._gateway_dmr_id))), (self._dmrgui, 34003))
|
get_subscriber_info(hex_str_3(self._gateway_dmr_id))), (self._dmrgui, 34003))
|
||||||
elif _cmd == 'eval':
|
elif _cmd == 'eval':
|
||||||
|
@ -614,11 +616,80 @@ class ambeIPSC(IPSC):
|
||||||
_ambe = _frame[33:52]
|
_ambe = _frame[33:52]
|
||||||
print('SLOT2:', h(_frame))
|
print('SLOT2:', h(_frame))
|
||||||
print("pt={:02X} pid={} seq={:02X} src={} dst={} ct={:02X} uk={} ci={} rsq={}".format(_packettype, _peerid,_ipsc_seq, _src_sub,_dst_sub,_call_type,_call_ctrl_info,_call_info,_rtp_seq))
|
print("pt={:02X} pid={} seq={:02X} src={} dst={} ct={:02X} uk={} ci={} rsq={}".format(_packettype, _peerid,_ipsc_seq, _src_sub,_dst_sub,_call_type,_call_ctrl_info,_call_info,_rtp_seq))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
from dmr_utils.utils import try_download, mk_id_dict
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
# ID ALIAS CREATION
|
||||||
networks[ipsc_network] = ambeIPSC(ipsc_network)
|
# Download
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
|
||||||
|
# Try updating peer aliases file
|
||||||
|
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||||
|
logger.info(result)
|
||||||
|
# Try updating subscriber aliases file
|
||||||
|
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||||
|
logger.info(result)
|
||||||
|
|
||||||
|
# Make Dictionaries
|
||||||
|
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
|
||||||
|
if peer_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
|
||||||
|
|
||||||
|
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
|
||||||
|
if subscriber_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
|
||||||
|
|
||||||
|
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
|
||||||
|
if talkgroup_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
|
||||||
|
|
||||||
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
|
def sig_handler(_signal, _frame):
|
||||||
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = ambeIPSC(system, CONFIG, logger)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
332
bridge.py
332
bridge.py
|
@ -18,18 +18,18 @@
|
||||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# This is a sample application to bridge traffic between IPSC networks. it uses
|
# This is a sample application to bridge traffic between IPSC systems. it uses
|
||||||
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
|
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
|
||||||
# configuration files. Both files have their own documentation for use.
|
# configuration files. Both files have their own documentation for use.
|
||||||
#
|
#
|
||||||
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
|
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
|
||||||
# determine which voice calls are bridged between IPSC networks and which are
|
# determine which voice calls are bridged between IPSC systems and which are
|
||||||
# not.
|
# not.
|
||||||
#
|
#
|
||||||
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
|
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
|
||||||
# used when you want bridge.py to be "polite" or serve as a backup bridge. If
|
# used when you want bridge.py to be "polite" or serve as a backup bridge. If
|
||||||
# a known bridge exists in either a source OR target IPSC network, then no
|
# a known bridge exists in either a source OR target IPSC network, then no
|
||||||
# bridging between those IPSC networks will take place. This behavior is
|
# bridging between those IPSC systems will take place. This behavior is
|
||||||
# dynamic and updates each keep-alive interval (main configuration file).
|
# dynamic and updates each keep-alive interval (main configuration file).
|
||||||
# For faster failover, configure a short keep-alive time and a low number of
|
# For faster failover, configure a short keep-alive time and a low number of
|
||||||
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
|
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
|
||||||
|
@ -46,14 +46,17 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
from binascii import b2a_hex as h
|
from binascii import b2a_hex as ahex
|
||||||
from time import time
|
from time import time
|
||||||
|
from importlib import import_module
|
||||||
# For debugging
|
|
||||||
from pprint import pprint
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from dmrlink import IPSC, NETWORK, networks, REPORTS, reporting_loop, dmr_nat, logger, hex_str_3, hex_str_4, int_id
|
|
||||||
|
from dmr_utils.utils import hex_str_3, hex_str_4, int_id
|
||||||
|
|
||||||
|
from dmrlink import IPSC, systems, config_reports
|
||||||
|
from ipsc.ipsc_const import BURST_DATA_TYPE
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -63,15 +66,6 @@ __maintainer__ = 'Cort Buffington, N0MJS'
|
||||||
__email__ = 'n0mjs@me.com'
|
__email__ = 'n0mjs@me.com'
|
||||||
|
|
||||||
|
|
||||||
# Constants for this application
|
|
||||||
#
|
|
||||||
BURST_DATA_TYPE = {
|
|
||||||
'VOICE_HEAD': '\x01',
|
|
||||||
'VOICE_TERM': '\x02',
|
|
||||||
'SLOT1_VOICE': '\x0A',
|
|
||||||
'SLOT2_VOICE': '\x8A'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Minimum time between different subscribers transmitting on the same TGID
|
# Minimum time between different subscribers transmitting on the same TGID
|
||||||
#
|
#
|
||||||
TS_CLEAR_TIME = .2
|
TS_CLEAR_TIME = .2
|
||||||
|
@ -81,79 +75,87 @@ TS_CLEAR_TIME = .2
|
||||||
# configuration file and listed as "active". It can be empty,
|
# configuration file and listed as "active". It can be empty,
|
||||||
# but it has to exist.
|
# but it has to exist.
|
||||||
#
|
#
|
||||||
try:
|
def build_rules(_bridge_rules):
|
||||||
from bridge_rules import RULES as RULES_FILE
|
try:
|
||||||
logger.info('Bridge rules file found and rules imported')
|
rule_file = import_module(_bridge_rules)
|
||||||
except ImportError:
|
logger.info('Bridge rules file found and rules imported')
|
||||||
sys.exit('Bridging rules file not found or invalid')
|
except ImportError:
|
||||||
|
sys.exit('Bridging rules file not found or invalid')
|
||||||
|
|
||||||
# Convert integer GROUP ID numbers from the config into hex strings
|
# Convert integer GROUP ID numbers from the config into hex strings
|
||||||
# we need to send in the actual data packets.
|
# we need to send in the actual data packets.
|
||||||
#
|
#
|
||||||
|
|
||||||
for _ipsc in RULES_FILE:
|
for _ipsc in rule_file.RULES:
|
||||||
for _rule in RULES_FILE[_ipsc]['GROUP_VOICE']:
|
for _rule in rule_file.RULES[_ipsc]['GROUP_VOICE']:
|
||||||
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
|
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
|
||||||
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
|
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
|
||||||
_rule['SRC_TS'] = _rule['SRC_TS']
|
_rule['SRC_TS'] = _rule['SRC_TS']
|
||||||
_rule['DST_TS'] = _rule['DST_TS']
|
_rule['DST_TS'] = _rule['DST_TS']
|
||||||
for i, e in enumerate(_rule['ON']):
|
for i, e in enumerate(_rule['ON']):
|
||||||
_rule['ON'][i] = hex_str_3(_rule['ON'][i])
|
_rule['ON'][i] = hex_str_3(_rule['ON'][i])
|
||||||
for i, e in enumerate(_rule['OFF']):
|
for i, e in enumerate(_rule['OFF']):
|
||||||
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
||||||
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
||||||
_rule['TIMER'] = time() + _rule['TIMEOUT']
|
_rule['TIMER'] = time() + _rule['TIMEOUT']
|
||||||
if _ipsc not in NETWORK:
|
if _ipsc not in CONFIG['SYSTEMS']:
|
||||||
sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
|
sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
|
||||||
for _ipsc in NETWORK:
|
for _ipsc in CONFIG['SYSTEMS']:
|
||||||
if _ipsc not in RULES_FILE:
|
if _ipsc not in rule_file.RULES:
|
||||||
sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
|
sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
|
||||||
|
|
||||||
RULES = RULES_FILE
|
return rule_file.RULES
|
||||||
|
|
||||||
# Import List of Bridges
|
# Import List of Bridges
|
||||||
# This is how we identify known bridges. If one of these is present
|
# This is how we identify known bridges. If one of these is present
|
||||||
# and it's mode byte is set to bridge, we don't
|
# and it's mode byte is set to bridge, we don't
|
||||||
#
|
#
|
||||||
try:
|
def build_bridges(_known_bridges):
|
||||||
from known_bridges import BRIDGES
|
try:
|
||||||
logger.info('Known bridges file found and bridge ID list imported ')
|
bridges_file = import_module(_known_bridges)
|
||||||
except ImportError:
|
logger.info('Known bridges file found and bridge ID list imported ')
|
||||||
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
|
return bridges_file.BRIDGES
|
||||||
BRIDGES = []
|
except ImportError:
|
||||||
|
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
# Import subscriber ACL
|
# Import subscriber ACL
|
||||||
# ACL may be a single list of subscriber IDs
|
# ACL may be a single list of subscriber IDs
|
||||||
# Global action is to allow or deny them. Multiple lists with different actions and ranges
|
# Global action is to allow or deny them. Multiple lists with different actions and ranges
|
||||||
# are not yet implemented.
|
# are not yet implemented.
|
||||||
try:
|
def build_acl(_sub_acl):
|
||||||
from sub_acl import ACL_ACTION, ACL
|
try:
|
||||||
# uses more memory to build hex strings, but processes MUCH faster when checking for matches
|
acl_file = import_module(_sub_acl)
|
||||||
for i, e in enumerate(ACL):
|
for i, e in enumerate(acl_file.ACL):
|
||||||
ACL[i] = hex_str_3(ACL[i])
|
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
|
||||||
logger.info('Subscriber access control file found, subscriber ACL imported')
|
logger.info('ACL file found and ACL entries imported')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.critical('\'sub_acl.py\' not found - all subscriber IDs are valid')
|
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||||
ACL_ACTION = 'NONE'
|
ACL_ACTION = 'NONE'
|
||||||
|
|
||||||
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
||||||
# define a differnet function to be used to check the ACL
|
# define a differnet function to be used to check the ACL
|
||||||
if ACL_ACTION == 'PERMIT':
|
global allow_sub
|
||||||
def allow_sub(_sub):
|
if acl_file.ACL_ACTION == 'PERMIT':
|
||||||
if _sub in ACL:
|
def allow_sub(_sub):
|
||||||
|
if _sub in ACL:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif acl_file.ACL_ACTION == 'DENY':
|
||||||
|
def allow_sub(_sub):
|
||||||
|
if _sub not in ACL:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
def allow_sub(_sub):
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
return acl_file.ACL
|
||||||
elif ACL_ACTION == 'DENY':
|
|
||||||
def allow_sub(_sub):
|
|
||||||
if _sub not in ACL:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
def allow_sub(_sub):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Run this every minute for rule timer updates
|
# Run this every minute for rule timer updates
|
||||||
def rule_timer_loop():
|
def rule_timer_loop():
|
||||||
logger.debug('(ALL IPSC) Rule timer loop started')
|
logger.debug('(ALL IPSC) Rule timer loop started')
|
||||||
|
@ -179,15 +181,17 @@ def rule_timer_loop():
|
||||||
else:
|
else:
|
||||||
logger.debug('Rule timer loop made no rule changes')
|
logger.debug('Rule timer loop made no rule changes')
|
||||||
|
|
||||||
|
|
||||||
class bridgeIPSC(IPSC):
|
class bridgeIPSC(IPSC):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, _name, _config, _logger, _bridges):
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
if BRIDGES:
|
self.BRIDGES = _bridges
|
||||||
logger.info('Initializing backup/polite bridging')
|
if self.BRIDGES:
|
||||||
|
self._logger.info('(%s) Initializing backup/polite bridging', self._system)
|
||||||
self.BRIDGE = False
|
self.BRIDGE = False
|
||||||
else:
|
else:
|
||||||
self.BRIDGE = True
|
self.BRIDGE = True
|
||||||
logger.info('Initializing standard bridging')
|
self._logger.info('Initializing standard bridging')
|
||||||
|
|
||||||
self.IPSC_STATUS = {
|
self.IPSC_STATUS = {
|
||||||
1: {'RX_GROUP':'\x00', 'TX_GROUP':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'},
|
1: {'RX_GROUP':'\x00', 'TX_GROUP':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'},
|
||||||
|
@ -199,31 +203,32 @@ class bridgeIPSC(IPSC):
|
||||||
|
|
||||||
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
|
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
|
||||||
|
|
||||||
if BRIDGES:
|
|
||||||
def startProtocol(self):
|
def startProtocol(self):
|
||||||
IPSC.startProtocol(self)
|
IPSC.startProtocol(self)
|
||||||
|
if self.BRIDGES:
|
||||||
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
|
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
|
||||||
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
|
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
|
||||||
|
|
||||||
# This is the backup/polite bridge maintenance loop
|
# This is the backup/polite bridge maintenance loop
|
||||||
def bridge_presence_loop(self):
|
def bridge_presence_loop(self):
|
||||||
|
self._logger.debug('(%s) Bridge presence loop initiated', self._system)
|
||||||
_temp_bridge = True
|
_temp_bridge = True
|
||||||
for peer in BRIDGES:
|
for peer in self.BRIDGES:
|
||||||
_peer = hex_str_4(peer)
|
_peer = hex_str_4(peer)
|
||||||
|
|
||||||
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
|
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
|
||||||
_temp_bridge = False
|
_temp_bridge = False
|
||||||
logger.debug('(%s) Peer %s is an active bridge', self._network, int_id(_peer))
|
self._logger.debug('(%s) Peer %s is an active bridge', self._system, int_id(_peer))
|
||||||
|
|
||||||
if _peer == self._master['RADIO_ID'] \
|
if _peer == self._master['RADIO_ID'] \
|
||||||
and self._master['STATUS']['CONNECTED'] \
|
and self._master['STATUS']['CONNECTED'] \
|
||||||
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
|
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
|
||||||
_temp_bridge = False
|
_temp_bridge = False
|
||||||
logger.debug('(%s) Master %s is an active bridge',self._network, int_id(_peer))
|
self._logger.debug('(%s) Master %s is an active bridge',self._system, int_id(_peer))
|
||||||
|
|
||||||
if self.BRIDGE != _temp_bridge:
|
if self.BRIDGE != _temp_bridge:
|
||||||
logger.info('(%s) Changing bridge status to: %s', self._network, _temp_bridge )
|
self._logger.info('(%s) Changing bridge status to: %s', self._system, _temp_bridge )
|
||||||
self.BRIDGE = _temp_bridge
|
self.BRIDGE = _temp_bridge
|
||||||
|
|
||||||
|
|
||||||
|
@ -231,32 +236,31 @@ class bridgeIPSC(IPSC):
|
||||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||||
#************************************************
|
#************************************************
|
||||||
#
|
#
|
||||||
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||||
# Check for ACL match, and return if the subscriber is not allowed
|
# Check for ACL match, and return if the subscriber is not allowed
|
||||||
if allow_sub(_src_sub) == False:
|
if allow_sub(_src_sub) == False:
|
||||||
logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Process the packet
|
# Process the packet
|
||||||
logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||||
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
|
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
|
||||||
_seq_id = _data[5]
|
_seq_id = _data[5]
|
||||||
_ts += 1
|
|
||||||
|
|
||||||
now = time() # Mark packet arrival time -- we'll need this for call contention handling
|
now = time() # Mark packet arrival time -- we'll need this for call contention handling
|
||||||
|
|
||||||
for rule in RULES[_network]['GROUP_VOICE']:
|
for rule in RULES[self._system]['GROUP_VOICE']:
|
||||||
_target = rule['DST_NET'] # Shorthand to reduce length and make it easier to read
|
_target = rule['DST_NET'] # Shorthand to reduce length and make it easier to read
|
||||||
_status = networks[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
|
_status = systems[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
|
||||||
|
|
||||||
# This is the primary rule match to determine if the call will be routed.
|
# This is the primary rule match to determine if the call will be routed.
|
||||||
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or networks[_target].BRIDGE == True):
|
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or systems[_target].BRIDGE == True):
|
||||||
|
|
||||||
#
|
#
|
||||||
# BEGIN CONTENTION HANDLING
|
# BEGIN CONTENTION HANDLING
|
||||||
#
|
#
|
||||||
# If this is an inter-DMRlink trunk, this isn't necessary
|
# If this is an inter-DMRlink trunk, this isn't necessary
|
||||||
if RULES[_network]['TRUNK'] == False:
|
if RULES[self._system]['TRUNK'] == False:
|
||||||
|
|
||||||
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
|
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
|
||||||
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime
|
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime
|
||||||
|
@ -265,27 +269,26 @@ class bridgeIPSC(IPSC):
|
||||||
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time
|
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time
|
||||||
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
|
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
|
||||||
#
|
#
|
||||||
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[_network]['GROUP_HANGTIME'])):
|
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
|
||||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
self._logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
||||||
continue
|
continue
|
||||||
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[_network]['GROUP_HANGTIME'])):
|
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
|
||||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
|
self._logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
|
||||||
continue
|
continue
|
||||||
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < TS_CLEAR_TIME):
|
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < TS_CLEAR_TIME):
|
||||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
self._logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
||||||
continue
|
continue
|
||||||
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['TX_GROUP']) and (_src_sub != _status[rule['DST_TS']]['TX_SRC_SUB']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < TS_CLEAR_TIME):
|
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['TX_GROUP']) and (_src_sub != _status[rule['DST_TS']]['TX_SRC_SUB']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < TS_CLEAR_TIME):
|
||||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', _network, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
|
self._logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', self._system, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
|
||||||
continue
|
continue
|
||||||
#
|
#
|
||||||
# END CONTENTION HANDLING
|
# END CONTENTION HANDLING
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# BEGIN FRAME FORWARDING
|
# BEGIN FRAME FORWARDING
|
||||||
#
|
#
|
||||||
|
@ -293,7 +296,7 @@ class bridgeIPSC(IPSC):
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
|
|
||||||
# Re-Write the IPSC SRC to match the target network's ID
|
# Re-Write the IPSC SRC to match the target network's ID
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG['SYSTEMS'][_target]['LOCAL']['RADIO_ID'])
|
||||||
|
|
||||||
# Re-Write the destination Group ID
|
# Re-Write the destination Group ID
|
||||||
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
|
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
|
||||||
|
@ -322,7 +325,7 @@ class bridgeIPSC(IPSC):
|
||||||
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
||||||
|
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
networks[_target].send_to_ipsc(_tmp_data)
|
systems[_target].send_to_ipsc(_tmp_data)
|
||||||
#
|
#
|
||||||
# END FRAME FORWARDING
|
# END FRAME FORWARDING
|
||||||
#
|
#
|
||||||
|
@ -350,105 +353,158 @@ class bridgeIPSC(IPSC):
|
||||||
if self.last_seq_id != _seq_id:
|
if self.last_seq_id != _seq_id:
|
||||||
self.last_seq_id = _seq_id
|
self.last_seq_id = _seq_id
|
||||||
self.call_start = time()
|
self.call_start = time()
|
||||||
logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
|
self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
|
||||||
|
|
||||||
# Action happens on un-key
|
# Action happens on un-key
|
||||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
|
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||||
if self.last_seq_id == _seq_id:
|
if self.last_seq_id == _seq_id:
|
||||||
self.call_duration = time() - self.call_start
|
self.call_duration = time() - self.call_start
|
||||||
logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
|
self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
|
||||||
else:
|
else:
|
||||||
logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
|
self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
|
||||||
|
|
||||||
# Iterate the rules dictionary
|
# Iterate the rules dictionary
|
||||||
for rule in RULES[_network]['GROUP_VOICE']:
|
for rule in RULES[self._system]['GROUP_VOICE']:
|
||||||
_target = rule['DST_NET']
|
_target = rule['DST_NET']
|
||||||
|
|
||||||
# TGID matches a rule source, reset its timer
|
# TGID matches a rule source, reset its timer
|
||||||
if _ts == rule['SRC_TS'] and _dst_group == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
|
if _ts == rule['SRC_TS'] and _dst_group == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
|
||||||
rule['TIMER'] = now + rule['TIMEOUT']
|
rule['TIMER'] = now + rule['TIMEOUT']
|
||||||
logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', _network, rule['NAME'], rule['TIMER'])
|
self._logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER'])
|
||||||
|
|
||||||
# Scan for reciprocal rules and reset their timers as well.
|
# Scan for reciprocal rules and reset their timers as well.
|
||||||
for target_rule in RULES[_target]['GROUP_VOICE']:
|
for target_rule in RULES[_target]['GROUP_VOICE']:
|
||||||
if target_rule['NAME'] == rule['NAME']:
|
if target_rule['NAME'] == rule['NAME']:
|
||||||
target_rule['TIMER'] = now + target_rule['TIMEOUT']
|
target_rule['TIMER'] = now + target_rule['TIMEOUT']
|
||||||
logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', _network, target_rule['NAME'], _target, rule['TIMER'])
|
self._logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER'])
|
||||||
|
|
||||||
# TGID matches an ACTIVATION trigger
|
# TGID matches an ACTIVATION trigger
|
||||||
if _dst_group in rule['ON']:
|
if _dst_group in rule['ON']:
|
||||||
# Set the matching rule as ACTIVE
|
# Set the matching rule as ACTIVE
|
||||||
rule['ACTIVE'] = True
|
rule['ACTIVE'] = True
|
||||||
rule['TIMER'] = now + rule['TIMEOUT']
|
rule['TIMER'] = now + rule['TIMEOUT']
|
||||||
logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE'])
|
self._logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
|
||||||
|
|
||||||
# Set reciprocal rules for other IPSCs as ACTIVE
|
# Set reciprocal rules for other IPSCs as ACTIVE
|
||||||
for target_rule in RULES[_target]['GROUP_VOICE']:
|
for target_rule in RULES[_target]['GROUP_VOICE']:
|
||||||
if target_rule['NAME'] == rule['NAME']:
|
if target_rule['NAME'] == rule['NAME']:
|
||||||
target_rule['ACTIVE'] = True
|
target_rule['ACTIVE'] = True
|
||||||
target_rule['TIMER'] = now + target_rule['TIMEOUT']
|
target_rule['TIMER'] = now + target_rule['TIMEOUT']
|
||||||
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE'])
|
self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
|
||||||
|
|
||||||
# TGID matches an DE-ACTIVATION trigger
|
# TGID matches an DE-ACTIVATION trigger
|
||||||
if _dst_group in rule['OFF']:
|
if _dst_group in rule['OFF']:
|
||||||
# Set the matching rule as ACTIVE
|
# Set the matching rule as ACTIVE
|
||||||
rule['ACTIVE'] = False
|
rule['ACTIVE'] = False
|
||||||
logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE'])
|
self._logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
|
||||||
|
|
||||||
# Set reciprocal rules for other IPSCs as ACTIVE
|
# Set reciprocal rules for other IPSCs as ACTIVE
|
||||||
_target = rule['DST_NET']
|
_target = rule['DST_NET']
|
||||||
for target_rule in RULES[_target]['GROUP_VOICE']:
|
for target_rule in RULES[_target]['GROUP_VOICE']:
|
||||||
if target_rule['NAME'] == rule['NAME']:
|
if target_rule['NAME'] == rule['NAME']:
|
||||||
target_rule['ACTIVE'] = False
|
target_rule['ACTIVE'] = False
|
||||||
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE'])
|
self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
|
||||||
#
|
#
|
||||||
# END IN-BAND SIGNALLING
|
# END IN-BAND SIGNALLING
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
self._logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||||
|
|
||||||
for target in RULES[_network]['GROUP_DATA']:
|
for target in RULES[self._system]['GROUP_DATA']:
|
||||||
|
|
||||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
# Re-Write the IPSC SRC to match the target network's ID
|
# Re-Write the IPSC SRC to match the target network's ID
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG[target]['LOCAL']['RADIO_ID'])
|
||||||
|
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
networks[target].send_to_ipsc(_tmp_data)
|
systems[target].send_to_ipsc(_tmp_data)
|
||||||
|
|
||||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
self._logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||||
|
|
||||||
for target in RULES[_network]['PRIVATE_DATA']:
|
for target in RULES[self._system]['PRIVATE_DATA']:
|
||||||
|
|
||||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
# Re-Write the IPSC SRC to match the target network's ID
|
# Re-Write the IPSC SRC to match the target network's ID
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG[target]['LOCAL']['RADIO_ID'])
|
||||||
|
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
networks[target].send_to_ipsc(_tmp_data)
|
systems[target].send_to_ipsc(_tmp_data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
from dmr_utils.utils import try_download, mk_id_dict
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
|
config_reports(CONFIG)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
|
|
||||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
for ipsc_network in NETWORK:
|
def sig_handler(_signal, _frame):
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
networks[ipsc_network] = bridgeIPSC(ipsc_network)
|
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
# Build the routing rules file
|
||||||
|
RULES = build_rules('bridge_rules')
|
||||||
|
|
||||||
|
# Build list of known bridge IDs
|
||||||
|
BRIDGES = build_bridges('known_bridges')
|
||||||
|
|
||||||
|
# Build the Access Control List
|
||||||
|
ACL = build_acl('sub_acl')
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = bridgeIPSC(system, CONFIG, logger, BRIDGES)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||||
if REPORTS['REPORT_NETWORKS']:
|
if CONFIG['REPORTS']['REPORT_NETWORKS']:
|
||||||
reporting = task.LoopingCall(reporting_loop)
|
reporting_loop = config_reports(CONFIG)
|
||||||
reporting.start(REPORTS['REPORT_INTERVAL'])
|
reporting = task.LoopingCall(reporting_loop, logger)
|
||||||
|
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
|
||||||
|
|
||||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||||
rule_timer = task.LoopingCall(rule_timer_loop)
|
rule_timer = task.LoopingCall(rule_timer_loop)
|
||||||
rule_timer.start(60)
|
rule_timer.start(60)
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
999
dmrlink.py
999
dmrlink.py
File diff suppressed because it is too large
Load Diff
|
@ -64,6 +64,23 @@ LOG_LEVEL: INFO
|
||||||
LOG_NAME: DMRlink
|
LOG_NAME: DMRlink
|
||||||
|
|
||||||
|
|
||||||
|
# DOWNLOAD AND IMPORT SUBSCRIBER, PEER and TGID ALIASES
|
||||||
|
# Ok, not the TGID, there's no master list I know of to download
|
||||||
|
# This is intended as a facility for other applcations built on top of
|
||||||
|
# HBlink to use, and will NOT be used in HBlink directly.
|
||||||
|
# STALE_DAYS is the number of days since the last download before we
|
||||||
|
# download again. Don't be an ass and change this to less than a few days.
|
||||||
|
[ALIASES]
|
||||||
|
TRY_DOWNLOAD: True
|
||||||
|
PATH: ./
|
||||||
|
PEER_FILE: peer_ids.csv
|
||||||
|
SUBSCRIBER_FILE: subscriber_ids.csv
|
||||||
|
TGID_FILE: talkgroup_ids.csv
|
||||||
|
PEER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=repeaters&format=csv&header=0
|
||||||
|
SUBSCRIBER_URL: http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?table=users&format=csv&header=0
|
||||||
|
STALE_DAYS: 7
|
||||||
|
|
||||||
|
|
||||||
# CONFIGURATION FOR IPSC NETWORKS
|
# CONFIGURATION FOR IPSC NETWORKS
|
||||||
# Please read these closely - catastrophic results could result by setting
|
# Please read these closely - catastrophic results could result by setting
|
||||||
# certain flags for things DMRlink cannot do.
|
# certain flags for things DMRlink cannot do.
|
||||||
|
|
|
@ -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))
|
|
@ -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 Values - assumed that cap+, etc. are different, this is all I can confirm
|
||||||
LINK_TYPE_IPSC = '\x04'
|
LINK_TYPE_IPSC = '\x04'
|
||||||
|
|
||||||
|
# Burst Data Types
|
||||||
|
BURST_DATA_TYPE = {
|
||||||
|
'VOICE_HEAD': '\x01',
|
||||||
|
'VOICE_TERM': '\x02',
|
||||||
|
'SLOT1_VOICE': '\x0A',
|
||||||
|
'SLOT2_VOICE': '\x8A'
|
||||||
|
}
|
||||||
|
|
||||||
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
|
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
|
||||||
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
|
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
|
||||||
|
|
129
log.py
129
log.py
|
@ -25,7 +25,8 @@ from twisted.internet import reactor
|
||||||
from binascii import b2a_hex as h
|
from binascii import b2a_hex as h
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
from dmrlink import IPSC, systems
|
||||||
|
from dmr_utils.utils import hex_str_3, hex_str_4, int_id, get_alias
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -36,39 +37,33 @@ __email__ = 'n0mjs@me.com'
|
||||||
|
|
||||||
|
|
||||||
class logIPSC(IPSC):
|
class logIPSC(IPSC):
|
||||||
|
def __init__(self, _name, _config, _logger):
|
||||||
def __init__(self, *args, **kwargs):
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
|
||||||
self.ACTIVE_CALLS = []
|
self.ACTIVE_CALLS = []
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||||
#************************************************
|
#************************************************
|
||||||
|
|
||||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
# _log = logger.debug
|
|
||||||
if (_ts not in self.ACTIVE_CALLS) or _end:
|
if (_ts not in self.ACTIVE_CALLS) or _end:
|
||||||
_time = time.strftime('%m/%d/%y %H:%M:%S')
|
_time = time.strftime('%m/%d/%y %H:%M:%S')
|
||||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
_dst_sub = get_alias(_dst_sub, talkgroup_ids)
|
||||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
_peerid = get_alias(_peerid, peer_ids)
|
||||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
_src_sub = get_alias(_src_sub, subscriber_ids)
|
||||||
if not _end: self.ACTIVE_CALLS.append(_ts)
|
if not _end: self.ACTIVE_CALLS.append(_ts)
|
||||||
if _end: self.ACTIVE_CALLS.remove(_ts)
|
if _end: self.ACTIVE_CALLS.remove(_ts)
|
||||||
|
|
||||||
if _ts: _ts = 2
|
|
||||||
else: _ts = 1
|
|
||||||
if _end: _end = 'END'
|
if _end: _end = 'END'
|
||||||
else: _end = 'START'
|
else: _end = 'START'
|
||||||
|
|
||||||
print('{} ({}) Call {} Group Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, _network, _end, _peerid, _src_sub, _dst_sub, _ts))
|
print('{} ({}) Call {} Group Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, self._system, _end, _peerid, _src_sub, _dst_sub, _ts))
|
||||||
|
|
||||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
# _log = logger.debug
|
|
||||||
if (_ts not in self.ACTIVE_CALLS) or _end:
|
if (_ts not in self.ACTIVE_CALLS) or _end:
|
||||||
_time = time.strftime('%m/%d/%y %H:%M:%S')
|
_time = time.strftime('%m/%d/%y %H:%M:%S')
|
||||||
_dst_sub = get_info(int_id(_dst_sub), subscriber_ids)
|
_dst_sub = get_alias(_dst_sub, subscriber_ids)
|
||||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
_peerid = get_alias(_peerid, peer_ids)
|
||||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
_src_sub = get_alias(_src_sub, subscriber_ids)
|
||||||
if not _end: self.ACTIVE_CALLS.append(_ts)
|
if not _end: self.ACTIVE_CALLS.append(_ts)
|
||||||
if _end: self.ACTIVE_CALLS.remove(_ts)
|
if _end: self.ACTIVE_CALLS.remove(_ts)
|
||||||
|
|
||||||
|
@ -77,25 +72,93 @@ class logIPSC(IPSC):
|
||||||
if _end: _end = 'END'
|
if _end: _end = 'END'
|
||||||
else: _end = 'START'
|
else: _end = 'START'
|
||||||
|
|
||||||
print('{} ({}) Call {} Private Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, _network, _end, _peerid, _src_sub, _dst_sub, _ts))
|
print('{} ({}) Call {} Private Voice: \n\tIPSC Source:\t{}\n\tSubscriber:\t{}\n\tDestination:\t{}\n\tTimeslot\t{}' .format(_time, self._system, _end, _peerid, _src_sub, _dst_sub, _ts))
|
||||||
|
|
||||||
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
_dst_sub = get_alias(_dst_sub, talkgroup_ids)
|
||||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
_peerid = get_alias(_peerid, peer_ids)
|
||||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
_src_sub = get_alias(_src_sub, subscriber_ids)
|
||||||
print('({}) Group Data Packet Received From: {}' .format(_network, _src_sub))
|
print('({}) Group Data Packet Received From: {}' .format(self._system, _src_sub))
|
||||||
|
|
||||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
_dst_sub = get_info(int_id(_dst_sub), subscriber_ids)
|
_dst_sub = get_alias(_dst_sub, subscriber_ids)
|
||||||
_peerid = get_info(int_id(_peerid), peer_ids)
|
_peerid = get_alias(_peerid, peer_ids)
|
||||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
_src_sub = get_alias(_src_sub, subscriber_ids)
|
||||||
print('({}) Private Data Packet Received From: {} To: {}' .format(_network, _src_sub, _dst_sub))
|
print('({}) Private Data Packet Received From: {} To: {}' .format(self._system, _src_sub, _dst_sub))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
from dmr_utils.utils import try_download, mk_id_dict
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
logger.info('DMRlink \'log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'log.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
# ID ALIAS CREATION
|
||||||
networks[ipsc_network] = logIPSC(ipsc_network)
|
# Download
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
|
||||||
|
# Try updating peer aliases file
|
||||||
|
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||||
|
logger.info(result)
|
||||||
|
# Try updating subscriber aliases file
|
||||||
|
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||||
|
logger.info(result)
|
||||||
|
|
||||||
|
# Make Dictionaries
|
||||||
|
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
|
||||||
|
if peer_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
|
||||||
|
|
||||||
|
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
|
||||||
|
if subscriber_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
|
||||||
|
|
||||||
|
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
|
||||||
|
if talkgroup_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
|
||||||
|
|
||||||
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
|
def sig_handler(_signal, _frame):
|
||||||
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = logIPSC(system, CONFIG, logger)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
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 __future__ import print_function
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from binascii import b2a_hex as h
|
|
||||||
|
|
||||||
import sys, time
|
import sys, time
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3
|
|
||||||
|
from dmrlink import IPSC, systems
|
||||||
|
|
||||||
|
from dmr_utils.utils import int_id, hex_str_3
|
||||||
|
from ipsc.ipsc_const import BURST_DATA_TYPE
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2014 - 2015 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2014 - 2015 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -45,14 +48,6 @@ __license__ = 'GNU GPLv3'
|
||||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||||
__email__ = 'n0mjs@me.com'
|
__email__ = 'n0mjs@me.com'
|
||||||
|
|
||||||
# Constants for this application
|
|
||||||
#
|
|
||||||
BURST_DATA_TYPE = {
|
|
||||||
'VOICE_HEAD': '\x01',
|
|
||||||
'VOICE_TERM': '\x02',
|
|
||||||
'SLOT1_VOICE': '\x0A',
|
|
||||||
'SLOT2_VOICE': '\x8A'
|
|
||||||
}
|
|
||||||
|
|
||||||
# path+filename for the transmission to play back
|
# path+filename for the transmission to play back
|
||||||
filename = '../test.pickle'
|
filename = '../test.pickle'
|
||||||
|
@ -66,9 +61,8 @@ trigger_groups_1 = ['\x00\x00\x01', '\x00\x00\x0D', '\x00\x00\x64']
|
||||||
trigger_groups_2 = ['\x00\x0C\x30',]
|
trigger_groups_2 = ['\x00\x0C\x30',]
|
||||||
|
|
||||||
class playIPSC(IPSC):
|
class playIPSC(IPSC):
|
||||||
|
def __init__(self, _name, _config, _logger):
|
||||||
def __init__(self, *args, **kwargs):
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
self.event_id = 1
|
self.event_id = 1
|
||||||
|
|
||||||
|
@ -76,30 +70,30 @@ class playIPSC(IPSC):
|
||||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||||
#************************************************
|
#************************************************
|
||||||
#
|
#
|
||||||
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||||
if _end:
|
if _end:
|
||||||
_self_peer = NETWORK[_network]['LOCAL']['RADIO_ID']
|
_self_peer = self._config['LOCAL']['RADIO_ID']
|
||||||
_self_src = _self_peer[1:]
|
_self_src = _self_peer[1:]
|
||||||
|
|
||||||
if (_peerid == _self_peer) or (_src_sub == _self_src):
|
if (_peerid == _self_peer) or (_src_sub == _self_src):
|
||||||
logger.error('(%s) Just received a packet that appears to have been originated by us. PeerID: %s Subscriber: %s TS: %s, TGID: %s', _network, int_id(_peerid), int_id(_src_sub), int(_ts)+1, int_id(_dst_group))
|
self._logger.error('(%s) Just received a packet that appears to have been originated by us. PeerID: %s Subscriber: %s TS: %s, TGID: %s', self._system, int_id(_peerid), int_id(_src_sub), int(_ts), int_id(_dst_group))
|
||||||
return
|
return
|
||||||
|
|
||||||
if trigger == False:
|
if trigger == False:
|
||||||
if (_ts == 0 and _dst_group not in trigger_groups_1) or (_ts == 1 and _dst_group not in trigger_groups_2):
|
if (_ts == 1 and _dst_group not in trigger_groups_1) or (_ts == 2 and _dst_group not in trigger_groups_2):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if (_ts == 0 and _dst_group in trigger_groups_1) or (_ts == 1 and _dst_group in trigger_groups_2):
|
if (_ts == 1 and _dst_group not in trigger_groups_1) or (_ts == 2 and _dst_group not in trigger_groups_2):
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info('(%s) Event ID: %s - Playback triggered from SourceID: %s, TS: %s, TGID: %s, PeerID: %s', _network, self.event_id, int_id(_src_sub), _ts+1, int_id(_dst_group), int_id(_peerid))
|
self._logger.info('(%s) Event ID: %s - Playback triggered from SourceID: %s, TS: %s, TGID: %s, PeerID: %s', self._system, self.event_id, int_id(_src_sub), _ts, int_id(_dst_group), int_id(_peerid))
|
||||||
|
|
||||||
# Determine the type of voice packet this is (see top of file for possible types)
|
# Determine the type of voice packet this is (see top of file for possible types)
|
||||||
_burst_data_type = _data[30]
|
_burst_data_type = _data[30]
|
||||||
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
self.CALL_DATA = pickle.load(open(filename, 'rb'))
|
self.CALL_DATA = pickle.load(open(filename, 'rb'))
|
||||||
logger.info('(%s) Event ID: %s - Playing back file: %s', _network, self.event_id, filename)
|
self._logger.info('(%s) Event ID: %s - Playing back file: %s', self._system, self.event_id, filename)
|
||||||
|
|
||||||
for i in self.CALL_DATA:
|
for i in self.CALL_DATA:
|
||||||
_tmp_data = i
|
_tmp_data = i
|
||||||
|
@ -113,9 +107,9 @@ class playIPSC(IPSC):
|
||||||
|
|
||||||
# Re-Write IPSC timeslot value
|
# Re-Write IPSC timeslot value
|
||||||
_call_info = int_id(_tmp_data[17:18])
|
_call_info = int_id(_tmp_data[17:18])
|
||||||
if _ts == 0:
|
if _ts == 1:
|
||||||
_call_info &= ~(1 << 5)
|
_call_info &= ~(1 << 5)
|
||||||
elif _ts == 1:
|
elif _ts == 2:
|
||||||
_call_info |= 1 << 5
|
_call_info |= 1 << 5
|
||||||
_call_info = chr(_call_info)
|
_call_info = chr(_call_info)
|
||||||
_tmp_data = _tmp_data[:17] + _call_info + _tmp_data[18:]
|
_tmp_data = _tmp_data[:17] + _call_info + _tmp_data[18:]
|
||||||
|
@ -124,9 +118,9 @@ class playIPSC(IPSC):
|
||||||
# Determine if the slot is present, so we can translate if need be
|
# Determine if the slot is present, so we can translate if need be
|
||||||
if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
||||||
# Re-Write timeslot if necessary...
|
# Re-Write timeslot if necessary...
|
||||||
if _ts == 0:
|
if _ts == 1:
|
||||||
_burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE']
|
_burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE']
|
||||||
elif _ts == 1:
|
elif _ts == 2:
|
||||||
_burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE']
|
_burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE']
|
||||||
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
||||||
|
|
||||||
|
@ -134,13 +128,58 @@ class playIPSC(IPSC):
|
||||||
self.send_to_ipsc(_tmp_data)
|
self.send_to_ipsc(_tmp_data)
|
||||||
time.sleep(0.06)
|
time.sleep(0.06)
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
logger.info('(%s) Event ID: %s - Playback Completed', _network, self.event_id)
|
self._logger.info('(%s) Event ID: %s - Playback Completed', self._system, self.event_id)
|
||||||
self.event_id = self.event_id + 1
|
self.event_id = self.event_id + 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
networks[ipsc_network] = playIPSC(ipsc_network)
|
def sig_handler(_signal, _frame):
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
reactor.run()
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = playIPSC(system, CONFIG, logger)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
|
reactor.run()
|
98
playback.py
98
playback.py
|
@ -22,10 +22,11 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from binascii import b2a_hex as h
|
from binascii import b2a_hex as ahex
|
||||||
|
|
||||||
import sys, time
|
import sys, time
|
||||||
from dmrlink import IPSC, NETWORK, networks, logger, dmr_nat, int_id, hex_str_3
|
from dmrlink import IPSC, systems
|
||||||
|
from dmr_utils.utils import int_id, hex_str_3
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -43,62 +44,66 @@ except ImportError:
|
||||||
HEX_TGID = hex_str_3(TGID)
|
HEX_TGID = hex_str_3(TGID)
|
||||||
HEX_SUB = hex_str_3(SUB)
|
HEX_SUB = hex_str_3(SUB)
|
||||||
BOGUS_SUB = '\xFF\xFF\xFF'
|
BOGUS_SUB = '\xFF\xFF\xFF'
|
||||||
if GROUP_SRC_SUB:
|
|
||||||
logger.info('Playback: USING SUBSCRIBER ID: %s FOR GROUP REPEAT', GROUP_SRC_SUB)
|
|
||||||
HEX_GRP_SUB = hex_str_3(GROUP_SRC_SUB)
|
|
||||||
|
|
||||||
class playbackIPSC(IPSC):
|
class playbackIPSC(IPSC):
|
||||||
|
def __init__(self, _name, _config, _logger):
|
||||||
def __init__(self, *args, **kwargs):
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
|
|
||||||
|
if GROUP_SRC_SUB:
|
||||||
|
self._logger.info('Playback: USING SUBSCRIBER ID: %s FOR GROUP REPEAT', GROUP_SRC_SUB)
|
||||||
|
self.GROUP_SRC_SUB = hex_str_3(GROUP_SRC_SUB)
|
||||||
|
|
||||||
|
if GROUP_REPEAT:
|
||||||
|
self._logger.info('Playback: GROUP REPEAT ENABLED')
|
||||||
|
|
||||||
|
if PRIVATE_REPEAT:
|
||||||
|
self._logger.info('Playback: PRIVATE REPEAT ENABLED')
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||||
#************************************************
|
#************************************************
|
||||||
#
|
#
|
||||||
if GROUP_REPEAT:
|
if GROUP_REPEAT:
|
||||||
logger.info('Playback: DEFINING GROUP REPEAT FUNCTION')
|
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
|
||||||
if HEX_TGID == _dst_sub and _ts in GROUP_TS:
|
if HEX_TGID == _dst_sub and _ts in GROUP_TS:
|
||||||
if not _end:
|
if not _end:
|
||||||
if not self.CALL_DATA:
|
if not self.CALL_DATA:
|
||||||
logger.info('(%s) Receiving transmission to be played back from subscriber: %s', _network, int_id(_src_sub))
|
self._logger.info('(%s) Receiving transmission to be played back from subscriber: %s', self._system, int_id(_src_sub))
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
#_tmp_data = dmr_nat(_data, _src_sub, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
#_tmp_data = dmr_nat(_data, _src_sub, self._config['LOCAL']['RADIO_ID'])
|
||||||
self.CALL_DATA.append(_tmp_data)
|
self.CALL_DATA.append(_tmp_data)
|
||||||
if _end:
|
if _end:
|
||||||
self.CALL_DATA.append(_data)
|
self.CALL_DATA.append(_data)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
logger.info('(%s) Playing back transmission from subscriber: %s', _network, int_id(_src_sub))
|
self._logger.info('(%s) Playing back transmission from subscriber: %s', self._system, int_id(_src_sub))
|
||||||
for i in self.CALL_DATA:
|
for i in self.CALL_DATA:
|
||||||
_tmp_data = i
|
_tmp_data = i
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, self._config['LOCAL']['RADIO_ID'])
|
||||||
if GROUP_SRC_SUB:
|
if GROUP_SRC_SUB:
|
||||||
_tmp_data = _tmp_data.replace(_src_sub, HEX_GRP_SUB)
|
_tmp_data = _tmp_data.replace(_src_sub, self.GROUP_SRC_SUB)
|
||||||
# Send the packet to all peers in the target IPSC
|
# Send the packet to all peers in the target IPSC
|
||||||
self.send_to_ipsc(_tmp_data)
|
self.send_to_ipsc(_tmp_data)
|
||||||
time.sleep(0.06)
|
time.sleep(0.06)
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
|
|
||||||
if PRIVATE_REPEAT:
|
if PRIVATE_REPEAT:
|
||||||
logger.info('Playback: DEFINING PRIVATE REPEAT FUNCTION')
|
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
|
||||||
if HEX_SUB == _dst_sub and _ts in PRIVATE_TS:
|
if HEX_SUB == _dst_sub and _ts in PRIVATE_TS:
|
||||||
if not _end:
|
if not _end:
|
||||||
if not self.CALL_DATA:
|
if not self.CALL_DATA:
|
||||||
logger.info('(%s) Receiving transmission to be played back from subscriber: %s, to subscriber: %s', _network, int_id(_src_sub), int_id(_dst_sub))
|
self._logger.info('(%s) Receiving transmission to be played back from subscriber: %s, to subscriber: %s', self._system, int_id(_src_sub), int_id(_dst_sub))
|
||||||
_tmp_data = _data
|
_tmp_data = _data
|
||||||
self.CALL_DATA.append(_tmp_data)
|
self.CALL_DATA.append(_tmp_data)
|
||||||
if _end:
|
if _end:
|
||||||
self.CALL_DATA.append(_data)
|
self.CALL_DATA.append(_data)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
logger.info('(%s) Playing back transmission from subscriber: %s, to subscriber %s', _network, int_id(_src_sub), int_id(_dst_sub))
|
self._logger.info('(%s) Playing back transmission from subscriber: %s, to subscriber %s', self._system, int_id(_src_sub), int_id(_dst_sub))
|
||||||
_orig_src = _src_sub
|
_orig_src = _src_sub
|
||||||
_orig_dst = _dst_sub
|
_orig_dst = _dst_sub
|
||||||
for i in self.CALL_DATA:
|
for i in self.CALL_DATA:
|
||||||
_tmp_data = i
|
_tmp_data = i
|
||||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
_tmp_data = _tmp_data.replace(_peerid, self._config['LOCAL']['RADIO_ID'])
|
||||||
_tmp_data = _tmp_data.replace(_dst_sub, BOGUS_SUB)
|
_tmp_data = _tmp_data.replace(_dst_sub, BOGUS_SUB)
|
||||||
_tmp_data = _tmp_data.replace(_src_sub, _orig_dst)
|
_tmp_data = _tmp_data.replace(_src_sub, _orig_dst)
|
||||||
_tmp_data = _tmp_data.replace(BOGUS_SUB, _orig_src)
|
_tmp_data = _tmp_data.replace(BOGUS_SUB, _orig_src)
|
||||||
|
@ -107,10 +112,55 @@ class playbackIPSC(IPSC):
|
||||||
time.sleep(0.06)
|
time.sleep(0.06)
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
networks[ipsc_network] = playbackIPSC(ipsc_network)
|
def sig_handler(_signal, _frame):
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = playbackIPSC(system, CONFIG, logger)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
115
rcm.py
115
rcm.py
|
@ -26,12 +26,13 @@ from __future__ import print_function
|
||||||
from twisted.internet.protocol import DatagramProtocol
|
from twisted.internet.protocol import DatagramProtocol
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
from binascii import b2a_hex as h
|
from binascii import b2a_hex as ahex
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import binascii
|
import binascii
|
||||||
import dmrlink
|
import dmrlink
|
||||||
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
from dmrlink import IPSC, systems
|
||||||
|
from dmr_utils.utils import get_alias, int_id
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -51,15 +52,14 @@ rpt = True
|
||||||
nack = True
|
nack = True
|
||||||
|
|
||||||
class rcmIPSC(IPSC):
|
class rcmIPSC(IPSC):
|
||||||
|
def __init__(self, _name, _config, _logger):
|
||||||
def __init__(self, *args, **kwargs):
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||||
#************************************************
|
#************************************************
|
||||||
#
|
#
|
||||||
def call_mon_status(self, _network, _data):
|
def call_mon_status(self, _data):
|
||||||
if not status:
|
if not status:
|
||||||
return
|
return
|
||||||
_source = _data[1:5]
|
_source = _data[1:5]
|
||||||
|
@ -73,19 +73,19 @@ class rcmIPSC(IPSC):
|
||||||
_prio = _data[23]
|
_prio = _data[23]
|
||||||
_sec = _data[24]
|
_sec = _data[24]
|
||||||
|
|
||||||
_source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids))
|
_source = str(int_id(_source)) + ', ' + str(get_alias(_source, peer_ids))
|
||||||
_ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_info(int_id(_ipsc_src), peer_ids))
|
_ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_alias(_ipsc_src, peer_ids))
|
||||||
_rf_src = str(int_id(_rf_src)) + ', ' + str(get_info(int_id(_rf_src), subscriber_ids))
|
_rf_src = str(int_id(_rf_src)) + ', ' + str(get_alias(_rf_src, subscriber_ids))
|
||||||
|
|
||||||
if _type == '\x4F' or '\x51':
|
if _type == '\x4F' or '\x51':
|
||||||
_rf_tgt = 'TGID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_info(int_id(_rf_tgt), talkgroup_ids))
|
_rf_tgt = 'TGID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_alias(_rf_tgt, talkgroup_ids))
|
||||||
else:
|
else:
|
||||||
_rf_tgt = 'SID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_info(int_id(_rf_tgt), subscriber_ids))
|
_rf_tgt = 'SID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_alias(_rf_tgt, subscriber_ids))
|
||||||
|
|
||||||
print('Call Monitor - Call Status')
|
print('Call Monitor - Call Status')
|
||||||
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
print('DATA SOURCE: ', _source)
|
print('DATA SOURCE: ', _source)
|
||||||
print('IPSC: ', _network)
|
print('IPSC: ', self._system)
|
||||||
print('IPSC Source: ', _ipsc_src)
|
print('IPSC Source: ', _ipsc_src)
|
||||||
print('Timeslot: ', TS[_ts])
|
print('Timeslot: ', TS[_ts])
|
||||||
try:
|
try:
|
||||||
|
@ -100,14 +100,14 @@ class rcmIPSC(IPSC):
|
||||||
print('Target Sub: ', _rf_tgt)
|
print('Target Sub: ', _rf_tgt)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def call_mon_rpt(self, _network, _data):
|
def call_mon_rpt(self, _data):
|
||||||
if not rpt:
|
if not rpt:
|
||||||
return
|
return
|
||||||
_source = _data[1:5]
|
_source = _data[1:5]
|
||||||
_ts1_state = _data[5]
|
_ts1_state = _data[5]
|
||||||
_ts2_state = _data[6]
|
_ts2_state = _data[6]
|
||||||
|
|
||||||
_source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids))
|
_source = str(int_id(_source)) + ', ' + str(get_alias(_source, peer_ids))
|
||||||
|
|
||||||
print('Call Monitor - Repeater State')
|
print('Call Monitor - Repeater State')
|
||||||
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
@ -123,13 +123,13 @@ class rcmIPSC(IPSC):
|
||||||
print('TS2 State (unknown): ', h(_ts2_state))
|
print('TS2 State (unknown): ', h(_ts2_state))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def call_mon_nack(self, _network, _data):
|
def call_mon_nack(self, _data):
|
||||||
if not nack:
|
if not nack:
|
||||||
return
|
return
|
||||||
_source = _data[1:5]
|
_source = _data[1:5]
|
||||||
_nack = _data[5]
|
_nack = _data[5]
|
||||||
|
|
||||||
_source = get_info(int_id(_source), peer_ids)
|
_source = get_alias(_source, peer_ids)
|
||||||
|
|
||||||
print('Call Monitor - Transmission NACK')
|
print('Call Monitor - Transmission NACK')
|
||||||
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||||
|
@ -140,17 +140,84 @@ class rcmIPSC(IPSC):
|
||||||
print('NACK Cause (unknown): ', h(_nack))
|
print('NACK Cause (unknown): ', h(_nack))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def repeater_wake_up(self, _network, _data):
|
def repeater_wake_up(self, _data):
|
||||||
_source = _data[1:5]
|
_source = _data[1:5]
|
||||||
_source_dec = int_id(_source)
|
_source_name = get_alias(_source, peer_ids)
|
||||||
_source_name = get_info(_source_dec, peer_ids)
|
print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(self._system, _source_name, int_id(_source)))
|
||||||
#print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(_network, _source_name, _source_dec))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
from dmr_utils.utils import try_download, mk_id_dict
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
# ID ALIAS CREATION
|
||||||
networks[ipsc_network] = rcmIPSC(ipsc_network)
|
# Download
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
|
||||||
|
# Try updating peer aliases file
|
||||||
|
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||||
|
logger.info(result)
|
||||||
|
# Try updating subscriber aliases file
|
||||||
|
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||||
|
logger.info(result)
|
||||||
|
|
||||||
|
# Make Dictionaries
|
||||||
|
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
|
||||||
|
if peer_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
|
||||||
|
|
||||||
|
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
|
||||||
|
if subscriber_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
|
||||||
|
|
||||||
|
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
|
||||||
|
if talkgroup_ids:
|
||||||
|
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
|
||||||
|
|
||||||
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
|
def sig_handler(_signal, _frame):
|
||||||
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = rcmIPSC(system, CONFIG, logger)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
80
record.py
80
record.py
|
@ -27,7 +27,8 @@ from binascii import b2a_hex as h
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3
|
from dmrlink import IPSC, systems
|
||||||
|
from dmr_utils.utils import hex_str_3, int_id
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
|
@ -49,11 +50,11 @@ while True:
|
||||||
ts = raw_input('Which timeslot (1, 2 or \'both\')? ')
|
ts = raw_input('Which timeslot (1, 2 or \'both\')? ')
|
||||||
if ts == '1' or ts == '2' or ts =='both':
|
if ts == '1' or ts == '2' or ts =='both':
|
||||||
if ts == '1':
|
if ts == '1':
|
||||||
ts = (0,)
|
|
||||||
if ts == '2':
|
|
||||||
ts = (1,)
|
ts = (1,)
|
||||||
|
if ts == '2':
|
||||||
|
ts = (2,)
|
||||||
if ts == 'both':
|
if ts == 'both':
|
||||||
ts = (0,1)
|
ts = (1,2)
|
||||||
break
|
break
|
||||||
print('...input must be \'1\', \'2\' or \'both\'')
|
print('...input must be \'1\', \'2\' or \'both\'')
|
||||||
|
|
||||||
|
@ -64,9 +65,8 @@ id = hex_str_3(id)
|
||||||
filename = raw_input('Filename to use for this recording? ')
|
filename = raw_input('Filename to use for this recording? ')
|
||||||
|
|
||||||
class recordIPSC(IPSC):
|
class recordIPSC(IPSC):
|
||||||
|
def __init__(self, _name, _config, _logger):
|
||||||
def __init__(self, *args, **kwargs):
|
IPSC.__init__(self, _name, _config, _logger)
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
|
@ -75,39 +75,83 @@ class recordIPSC(IPSC):
|
||||||
#
|
#
|
||||||
if tx_type == 'g':
|
if tx_type == 'g':
|
||||||
print('Initializing to record GROUP VOICE transmission')
|
print('Initializing to record GROUP VOICE transmission')
|
||||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
if id == _dst_sub and _ts in ts:
|
if id == _dst_sub and _ts in ts:
|
||||||
if not _end:
|
if not _end:
|
||||||
if not self.CALL_DATA:
|
if not self.CALL_DATA:
|
||||||
print('({}) Recording transmission from subscriber: {}' .format(_network, int_id(_src_sub)))
|
print('({}) Recording transmission from subscriber: {}' .format(self._system, int_id(_src_sub)))
|
||||||
self.CALL_DATA.append(_data)
|
self.CALL_DATA.append(_data)
|
||||||
if _end:
|
if _end:
|
||||||
self.CALL_DATA.append(_data)
|
self.CALL_DATA.append(_data)
|
||||||
print('({}) Transmission ended, writing to disk: {}' .format(_network, filename))
|
print('({}) Transmission ended, writing to disk: {}' .format(self._system, filename))
|
||||||
pickle.dump(self.CALL_DATA, open(filename, 'wb'))
|
pickle.dump(self.CALL_DATA, open(filename, 'wb'))
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
print('Recording created, program terminating')
|
print('Recording created, program terminating')
|
||||||
|
|
||||||
if tx_type == 'p':
|
if tx_type == 'p':
|
||||||
print('Initializing ro record PRIVATE VOICE transmission')
|
print('Initializing ro record PRIVATE VOICE transmission')
|
||||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
if id == _dst_sub and _ts in ts:
|
if id == _dst_sub and _ts in ts:
|
||||||
if not _end:
|
if not _end:
|
||||||
if not self.CALL_DATA:
|
if not self.CALL_DATA:
|
||||||
print('({}) Recording transmission from subscriber: {}' .format(_network, int_id(_src_sub)))
|
print('({}) Recording transmission from subscriber: {}' .format(self._system, int_id(_src_sub)))
|
||||||
self.CALL_DATA.append(_data)
|
self.CALL_DATA.append(_data)
|
||||||
if _end:
|
if _end:
|
||||||
self.CALL_DATA.append(_data)
|
self.CALL_DATA.append(_data)
|
||||||
print('({}) Transmission ended, writing to disk: {}' .format(_network, filename))
|
print('({}) Transmission ended, writing to disk: {}' .format(self._system, filename))
|
||||||
pickle.dump(self.CALL_DATA, open(filename, 'wb'))
|
pickle.dump(self.CALL_DATA, open(filename, 'wb'))
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
print('Recording created, program terminating')
|
print('Recording created, program terminating')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
|
||||||
|
import dmrlink_log
|
||||||
|
import dmrlink_config
|
||||||
|
|
||||||
|
# Change the current directory to the location of the application
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||||
|
|
||||||
|
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||||
|
cli_args = parser.parse_args()
|
||||||
|
|
||||||
|
if not cli_args.CFG_FILE:
|
||||||
|
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||||
|
|
||||||
|
# Call the external routine to build the configuration dictionary
|
||||||
|
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||||
|
|
||||||
|
# Call the external routing to start the system logger
|
||||||
|
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||||
|
|
||||||
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
|
||||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
# Shut ourselves down gracefully with the IPSC peers.
|
||||||
networks[ipsc_network] = recordIPSC(ipsc_network)
|
def sig_handler(_signal, _frame):
|
||||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
this_ipsc = systems[system]
|
||||||
|
logger.info('De-Registering from IPSC %s', system)
|
||||||
|
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||||
|
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||||
|
reactor.stop()
|
||||||
|
|
||||||
|
# Set signal handers so that we can gracefully exit if need be
|
||||||
|
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||||
|
signal.signal(sig, sig_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||||
|
for system in CONFIG['SYSTEMS']:
|
||||||
|
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||||
|
systems[system] = recordIPSC(system, CONFIG, logger)
|
||||||
|
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||||
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
75733
subscriber_ids.csv
75733
subscriber_ids.csv
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +1 @@
|
||||||
Worldwide,1
|
1,Worldwide
2,Local
3,North America
9,BrandMeister
13,Worldwide English
310,TAC 310
3100,DCI Bridge 2
3160,DCI 1
3169,Midwest
3172,Northeast
3174,Southeast
3112,Flordia
3120,Kansas Statewide
3125,Massachussetts
3129,Missouri
31201,BYRG KC
3777215,DCI Comm 1
9998,Echo Server
|
||||||
Local,2
|
|
||||||
North America,3
|
|
||||||
BrandMeister,9
|
|
||||||
Worldwide English,13
|
|
||||||
TAC 310,310
|
|
||||||
DCI Bridge 2,3100
|
|
||||||
DCI 1,3160
|
|
||||||
Midwest,3169
|
|
||||||
Northeast,3172
|
|
||||||
Southeast,3174
|
|
||||||
Flordia,3112
|
|
||||||
Kansas Statewide,3120
|
|
||||||
Massachussetts,3125
|
|
||||||
Missouri,3129
|
|
||||||
DCI Comm 1,3777215
|
|
||||||
Echo Server,9998
|
|
|
Loading…
Reference in New Issue