ALL ARE WORKING EXCEPT ambe_audio.py

This commit is contained in:
Cort Buffington 2016-12-18 21:51:13 -06:00
parent 9c6de3b902
commit 53c4ff5b23
14 changed files with 53109 additions and 28608 deletions

View File

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

View File

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

108
bridge.py
View File

@ -114,10 +114,11 @@ def build_bridges(_known_bridges):
try:
bridges_file = import_module(_known_bridges)
logger.info('Known bridges file found and bridge ID list imported ')
return bridges_file.BRIDGES
except ImportError:
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
bridges_file = []
return bridges_file
return []
# Import subscriber ACL
# ACL may be a single list of subscriber IDs
@ -180,16 +181,17 @@ def rule_timer_loop():
else:
logger.debug('Rule timer loop made no rule changes')
class bridgeIPSC(IPSC):
def __init__(self, _name, _config, _logger, _bridges):
IPSC.__init__(self, _name, _config, _logger, _bridges)
IPSC.__init__(self, _name, _config, _logger)
self.BRIDGES = _bridges
if self.BRIDGES:
logger.info('Initializing backup/polite bridging')
self._logger.info('(%s) Initializing backup/polite bridging', self._system)
self.BRIDGE = False
else:
self.BRIDGE = True
logger.info('Initializing standard bridging')
self._logger.info('Initializing standard bridging')
self.IPSC_STATUS = {
1: {'RX_GROUP':'\x00', 'TX_GROUP':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'},
@ -199,33 +201,34 @@ class bridgeIPSC(IPSC):
self.last_seq_id = '\x00'
self.call_start = 0
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
def startProtocol(self):
IPSC.startProtocol(self)
if self.BRIDGES:
def startProtocol(self):
IPSC.startProtocol(self)
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
# This is the backup/polite bridge maintenance loop
def bridge_presence_loop(self):
self._logger.debug('(%s) Bridge presence loop initiated', self._system)
_temp_bridge = True
for peer in BRIDGES:
for peer in self.BRIDGES:
_peer = hex_str_4(peer)
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
_temp_bridge = False
logger.debug('(%s) Peer %s is an active bridge', self._network, int_id(_peer))
self._logger.debug('(%s) Peer %s is an active bridge', self._system, int_id(_peer))
if _peer == self._master['RADIO_ID'] \
and self._master['STATUS']['CONNECTED'] \
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
_temp_bridge = False
logger.debug('(%s) Master %s is an active bridge',self._network, int_id(_peer))
self._logger.debug('(%s) Master %s is an active bridge',self._system, int_id(_peer))
if self.BRIDGE != _temp_bridge:
logger.info('(%s) Changing bridge status to: %s', self._network, _temp_bridge )
self._logger.info('(%s) Changing bridge status to: %s', self._system, _temp_bridge )
self.BRIDGE = _temp_bridge
@ -233,24 +236,23 @@ class bridgeIPSC(IPSC):
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
#
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
# Check for ACL match, and return if the subscriber is not allowed
if allow_sub(_src_sub) == False:
logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
return
# Process the packet
logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
_seq_id = _data[5]
_ts += 1
now = time() # Mark packet arrival time -- we'll need this for call contention handling
for rule in RULES[_network]['GROUP_VOICE']:
for rule in RULES[self._system]['GROUP_VOICE']:
_target = rule['DST_NET'] # Shorthand to reduce length and make it easier to read
_status = systems[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
# This is the primary rule match to determine if the call will be routed.
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or systems[_target].BRIDGE == True):
@ -258,7 +260,7 @@ class bridgeIPSC(IPSC):
# BEGIN CONTENTION HANDLING
#
# If this is an inter-DMRlink trunk, this isn't necessary
if RULES[_network]['TRUNK'] == False:
if RULES[self._system]['TRUNK'] == False:
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime
@ -267,27 +269,26 @@ class bridgeIPSC(IPSC):
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
#
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[_network]['GROUP_HANGTIME'])):
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
self._logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
continue
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[_network]['GROUP_HANGTIME'])):
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
self._logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
continue
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
self._logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
continue
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['TX_GROUP']) and (_src_sub != _status[rule['DST_TS']]['TX_SRC_SUB']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < TS_CLEAR_TIME):
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', _network, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
self._logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', self._system, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
continue
#
# END CONTENTION HANDLING
#
#
# BEGIN FRAME FORWARDING
#
@ -295,7 +296,7 @@ class bridgeIPSC(IPSC):
_tmp_data = _data
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG['SYSTEMS'][_target]['LOCAL']['RADIO_ID'])
# Re-Write the destination Group ID
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
@ -352,91 +353,90 @@ class bridgeIPSC(IPSC):
if self.last_seq_id != _seq_id:
self.last_seq_id = _seq_id
self.call_start = time()
logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
# Action happens on un-key
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
if self.last_seq_id == _seq_id:
self.call_duration = time() - self.call_start
logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
else:
logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
# Iterate the rules dictionary
for rule in RULES[_network]['GROUP_VOICE']:
for rule in RULES[self._system]['GROUP_VOICE']:
_target = rule['DST_NET']
# TGID matches a rule source, reset its timer
if _ts == rule['SRC_TS'] and _dst_group == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
rule['TIMER'] = now + rule['TIMEOUT']
logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', _network, rule['NAME'], rule['TIMER'])
self._logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER'])
# Scan for reciprocal rules and reset their timers as well.
for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']:
target_rule['TIMER'] = now + target_rule['TIMEOUT']
logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', _network, target_rule['NAME'], _target, rule['TIMER'])
self._logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER'])
# TGID matches an ACTIVATION trigger
if _dst_group in rule['ON']:
# Set the matching rule as ACTIVE
rule['ACTIVE'] = True
rule['TIMER'] = now + rule['TIMEOUT']
logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE'])
self._logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
# Set reciprocal rules for other IPSCs as ACTIVE
for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']:
target_rule['ACTIVE'] = True
target_rule['TIMER'] = now + target_rule['TIMEOUT']
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE'])
self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
# TGID matches an DE-ACTIVATION trigger
if _dst_group in rule['OFF']:
# Set the matching rule as ACTIVE
rule['ACTIVE'] = False
logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE'])
self._logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
# Set reciprocal rules for other IPSCs as ACTIVE
_target = rule['DST_NET']
for target_rule in RULES[_target]['GROUP_VOICE']:
if target_rule['NAME'] == rule['NAME']:
target_rule['ACTIVE'] = False
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE'])
self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
#
# END IN-BAND SIGNALLING
#
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
self._logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
for target in RULES[_network]['GROUP_DATA']:
for target in RULES[self._system]['GROUP_DATA']:
if self.BRIDGE == True or systems[target].BRIDGE == True:
_tmp_data = _data
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG[target]['LOCAL']['RADIO_ID'])
# Send the packet to all peers in the target IPSC
systems[target].send_to_ipsc(_tmp_data)
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
self._logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
for target in RULES[_network]['PRIVATE_DATA']:
for target in RULES[self._system]['PRIVATE_DATA']:
if self.BRIDGE == True or systems[target].BRIDGE == True:
_tmp_data = _data
# Re-Write the IPSC SRC to match the target network's ID
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG[target]['LOCAL']['RADIO_ID'])
# Send the packet to all peers in the target IPSC
systems[target].send_to_ipsc(_tmp_data)
if __name__ == '__main__':
if __name__ == '__main__':
import argparse
import os
import signal
@ -486,7 +486,7 @@ if __name__ == '__main__':
# Build list of known bridge IDs
BRIDGES = build_bridges('known_bridges')
# Build the Access Control List
ACL = build_acl('sub_acl')
@ -494,13 +494,13 @@ if __name__ == '__main__':
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
systems[system] = IPSC(system, CONFIG, logger)
systems[system] = bridgeIPSC(system, CONFIG, logger, BRIDGES)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS']:
config_reporting_loop(CONFIG['REPORTS']['REPORT_NETWORKS'])
reporting = task.LoopingCall(reporting_loop)
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
# INITIALIZE THE REPORTING LOOP IF CONFIGURED

View File

@ -65,64 +65,32 @@ __email__ = 'n0mjs@me.com'
# Global variables used whether we are a module or __main__
systems = {}
# Utility functions
def config_reporting_loop(_type):
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
global reporting_loop
if _type == 'PICKLE':
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (PICKLE)')
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config):
if _config['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (PICKLE)')
try:
with open(CONFIG['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
pickle_dump(CONFIG['SYSTEMS'], file, 2)
with open(_config['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
pickle_dump(_config['SYSTEMS'], file, 2)
file.close()
except IOError as detail:
logger.error('I/O Error: %s', detail)
_logger.error('I/O Error: %s', detail)
elif _type == 'PRINT':
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in CONFIG['SYSTEMS']:
print_master(system)
print_peer_list(system)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']:
print_master(_config, system)
print_peer_list(_config, system)
else:
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (NULL)')
# Determine if the provided peer ID is valid for the provided network
#
def valid_peer(_peer_list, _peerid):
if _peerid in _peer_list:
return True
return False
# Determine if the provided master ID is valid for the provided network
#
def valid_master(_network, _peerid):
if CONFIG['SYSTEMS'][_network]['MASTER']['RADIO_ID'] == _peerid:
return True
else:
return False
# De-register a peer from an IPSC by removing it's information
#
def de_register_peer(_network, _peerid):
# Iterate for the peer in our data
if _peerid in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
del CONFIG['SYSTEMS'][_network]['PEERS'][_peerid]
logger.info('(%s) Peer De-Registration Requested for: %s', _network, int_id(_peerid))
return
else:
logger.warning('(%s) Peer De-Registration Requested for: %s, but we don\'t have a listing for this peer', _network, int_id(_peerid))
pass
def reporting_loop(_logger):
_logger.debug('Periodic Reporting Loop Started (NULL)')
return reporting_loop
# Process the MODE byte in registration/peer list packets for determining master and peer capabilities
#
@ -153,7 +121,6 @@ def process_mode_byte(_hex_mode):
'TS_2': _ts2
}
# Process the FLAGS bytes in registration replies for determining what services are available
#
def process_flags_bytes(_hex_flags):
@ -184,73 +151,6 @@ def process_flags_bytes(_hex_flags):
'MASTER': _master
}
# Take a received peer list and the network it belongs to, process and populate the
# data structure in my_ipsc_config with the results, and return a simple list of peers.
#
def process_peer_list(_data, _network):
# Create a temporary peer list to track who we should have in our list -- used to find old peers we should remove.
_temp_peers = []
# Determine the length of the peer list for the parsing iterator
_peer_list_length = int(ahex(_data[5:7]), 16)
# Record the number of peers in the data structure... we'll use it later (11 bytes per peer entry)
CONFIG['SYSTEMS'][_network]['LOCAL']['NUM_PEERS'] = _peer_list_length/11
logger.info('(%s) Peer List Received from Master: %s peers in this IPSC', _network, CONFIG['SYSTEMS'][_network]['LOCAL']['NUM_PEERS'])
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
for i in range(7, _peer_list_length +7, 11):
# Extract various elements from each entry...
_hex_radio_id = (_data[i:i+4])
_hex_address = (_data[i+4:i+8])
_ip_address = IPAddr(_hex_address)
_hex_port = (_data[i+8:i+10])
_port = int(ahex(_hex_port), 16)
_hex_mode = (_data[i+10:i+11])
# Add this peer to a temporary PeerID list - used to remove any old peers no longer with us
_temp_peers.append(_hex_radio_id)
# This is done elsewhere for the master too, so we use a separate function
_decoded_mode = process_mode_byte(_hex_mode)
# If this entry WAS already in our list, update everything except the stats
# in case this was a re-registration with a different mode, flags, etc.
if _hex_radio_id in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['IP'] = _ip_address
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['PORT'] = _port
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['MODE'] = _hex_mode
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['MODE_DECODE'] = _decoded_mode
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['FLAGS'] = ''
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id]['FLAGS_DECODE'] = ''
logger.debug('(%s) Peer Updated: %s', _network, CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id])
# If this entry was NOT already in our list, add it.
if _hex_radio_id not in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id] = {
'IP': _ip_address,
'PORT': _port,
'MODE': _hex_mode,
'MODE_DECODE': _decoded_mode,
'FLAGS': '',
'FLAGS_DECODE': '',
'STATUS': {
'CONNECTED': False,
'KEEP_ALIVES_SENT': 0,
'KEEP_ALIVES_MISSED': 0,
'KEEP_ALIVES_OUTSTANDING': 0,
'KEEP_ALIVES_RECEIVED': 0,
'KEEP_ALIVE_RX_TIME': 0
}
}
logger.debug('(%s) Peer Added: %s', _network, CONFIG['SYSTEMS'][_network]['PEERS'][_hex_radio_id])
# Finally, check to see if there's a peer already in our list that was not in this peer list
# and if so, delete it.
for peer in CONFIG['SYSTEMS'][_network]['PEERS'].keys():
if peer not in _temp_peers:
de_register_peer(_network, peer)
logger.warning('(%s) Peer Deleted (not in new peer list): %s', _network, int_id(peer))
# Build a peer list - used when a peer registers, re-regiseters or times out
#
def build_peer_list(_peers):
@ -266,13 +166,13 @@ def build_peer_list(_peers):
# Gratuitous print-out of the peer list.. Pretty much debug stuff.
#
def print_peer_list(_network):
_peers = CONFIG['SYSTEMS'][_network]['PEERS']
def print_peer_list(_config, _network):
_peers = _config['SYSTEMS'][_network]['PEERS']
_status = CONFIG['SYSTEMS'][_network]['MASTER']['STATUS']['PEER_LIST']
_status = _config['SYSTEMS'][_network]['MASTER']['STATUS']['PEER_LIST']
#print('Peer List Status for {}: {}' .format(_network, _status))
if _status and not CONFIG['SYSTEMS'][_network]['PEERS']:
if _status and not _config['SYSTEMS'][_network]['PEERS']:
print('We are the only peer for: %s' % _network)
print('')
return
@ -282,18 +182,18 @@ def print_peer_list(_network):
_this_peer = _peers[peer]
_this_peer_stat = _this_peer['STATUS']
if peer == CONFIG['SYSTEMS'][_network]['LOCAL']['RADIO_ID']:
if peer == _config['SYSTEMS'][_network]['LOCAL']['RADIO_ID']:
me = '(self)'
else:
me = ''
print('\tRADIO ID: {} {}' .format(int_id(peer), me))
print('\t\tIP Address: {}:{}' .format(_this_peer['IP'], _this_peer['PORT']))
if _this_peer['MODE_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_MODE']:
if _this_peer['MODE_DECODE'] and _config['REPORTS']['PRINT_PEERS_INC_MODE']:
print('\t\tMode Values:')
for name, value in _this_peer['MODE_DECODE'].items():
print('\t\t\t{}: {}' .format(name, value))
if _this_peer['FLAGS_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_FLAGS']:
if _this_peer['FLAGS_DECODE'] and _config['REPORTS']['PRINT_PEERS_INC_FLAGS']:
print('\t\tService Flags:')
for name, value in _this_peer['FLAGS_DECODE'].items():
print('\t\t\t{}: {}' .format(name, value))
@ -304,51 +204,24 @@ def print_peer_list(_network):
# Gratuitous print-out of Master info.. Pretty much debug stuff.
#
def print_master(_network):
if CONFIG['SYSTEMS'][_network]['LOCAL']['MASTER_PEER']:
print('DMRlink is the Master for %s'.format(_network))
def print_master(_config, _network):
if _config['SYSTEMS'][_network]['LOCAL']['MASTER_PEER']:
print('DMRlink is the Master for %s' % _network)
else:
_master = CONFIG['SYSTEMS'][_network]['MASTER']
_master = _config['SYSTEMS'][_network]['MASTER']
print('Master for %s' % _network)
print('\tRADIO ID: {}'.format(int(ahex(_master['RADIO_ID']), 16)))
if _master['MODE_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_MODE']:
print('\tRADIO ID: {}' .format(int(ahex(_master['RADIO_ID']), 16)))
if _master['MODE_DECODE'] and _config['REPORTS']['PRINT_PEERS_INC_MODE']:
print('\t\tMode Values:')
for name, value in _master['MODE_DECODE'].items():
print('\t\t\t{}: {}'.format(name, value))
if _master['FLAGS_DECODE'] and CONFIG['REPORTS']['PRINT_PEERS_INC_FLAGS']:
print('\t\t\t{}: {}' .format(name, value))
if _master['FLAGS_DECODE'] and _config['REPORTS']['PRINT_PEERS_INC_FLAGS']:
print('\t\tService Flags:')
for name, value in _master['FLAGS_DECODE'].items():
print('\t\t\t{}: {}'.format(name, value))
print('\t\t\t{}: {}' .format(name, value))
print('\t\tStatus: {}, KeepAlives Sent: {}, KeepAlives Outstanding: {}, KeepAlives Missed: {}' .format(_master['STATUS']['CONNECTED'], _master['STATUS']['KEEP_ALIVES_SENT'], _master['STATUS']['KEEP_ALIVES_OUTSTANDING'], _master['STATUS']['KEEP_ALIVES_MISSED']))
print('\t\t KeepAlives Received: {}, Last KeepAlive Received at: {}' .format(_master['STATUS']['KEEP_ALIVES_RECEIVED'], _master['STATUS']['KEEP_ALIVE_RX_TIME']))
# Timed loop used for reporting IPSC status
#
# REPORT BASED ON THE TYPE SELECTED IN THE MAIN CONFIG FILE
def config_reports(_config):
global reporting_loop
if _config['REPORTS']['REPORT_NETWORKS'] == 'PICKLE':
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (PICKLE)')
try:
with open(_config['REPORTS']['REPORT_PATH']+'dmrlink_stats.pickle', 'wb') as file:
pickle_dump(_config['SYSTEMS'], file, 2)
file.close()
except IOError as detail:
logger.error('I/O Error: %s', detail)
elif _config['REPORTS']['REPORT_NETWORKS'] == 'PRINT':
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (PRINT)')
for system in _config['SYSTEMS']:
print_master(system)
print_peer_list(system)
else:
def reporting_loop():
logger.debug('Periodic Reporting Loop Started (NULL)')
#************************************************
@ -407,42 +280,140 @@ class IPSC(DatagramProtocol):
self.DE_REG_REQ_PKT = (DE_REG_REQ + self._local_id)
self.DE_REG_REPLY_PKT = (DE_REG_REPLY + self._local_id)
#
logger.info('(%s) IPSC Instance Created: %s, %s:%s', self._system, int_id(self._local['RADIO_ID']), self._local['IP'], self._local['PORT'])
self._logger.info('(%s) IPSC Instance Created: %s, %s:%s', self._system, int_id(self._local['RADIO_ID']), self._local['IP'], self._local['PORT'])
#******************************************************
# SUPPORT FUNCTIONS FOR HANDLING IPSC OPERATIONS
#******************************************************
# Determine if the provided peer ID is valid for the provided network
#
def valid_peer(self, _peerid):
if _peerid in self._peers:
return True
return False
# Determine if the provided master ID is valid for the provided network
#
def valid_master(self, _peerid):
if self._master['RADIO_ID'] == _peerid:
return True
else:
return False
# De-register a peer from an IPSC by removing it's information
#
def de_register_peer(self, _peerid):
# Iterate for the peer in our data
if _peerid in self._peers.keys():
del self._peers[_peerid]
logger.info('(%s) Peer De-Registration Requested for: %s', self._system, int_id(_peerid))
return
else:
logger.warning('(%s) Peer De-Registration Requested for: %s, but we don\'t have a listing for this peer', self._system, int_id(_peerid))
pass
# Take a received peer list and the network it belongs to, process and populate the
# data structure in my_ipsc_config with the results, and return a simple list of peers.
#
def process_peer_list(self, _data):
# Create a temporary peer list to track who we should have in our list -- used to find old peers we should remove.
_temp_peers = []
# Determine the length of the peer list for the parsing iterator
_peer_list_length = int(ahex(_data[5:7]), 16)
# Record the number of peers in the data structure... we'll use it later (11 bytes per peer entry)
self._local['NUM_PEERS'] = _peer_list_length/11
self._logger.info('(%s) Peer List Received from Master: %s peers in this IPSC', self._system, self._local['NUM_PEERS'])
# Iterate each peer entry in the peer list. Skip the header, then pull the next peer, the next, etc.
for i in range(7, _peer_list_length +7, 11):
# Extract various elements from each entry...
_hex_radio_id = (_data[i:i+4])
_hex_address = (_data[i+4:i+8])
_ip_address = IPAddr(_hex_address)
_hex_port = (_data[i+8:i+10])
_port = int(ahex(_hex_port), 16)
_hex_mode = (_data[i+10:i+11])
# Add this peer to a temporary PeerID list - used to remove any old peers no longer with us
_temp_peers.append(_hex_radio_id)
# This is done elsewhere for the master too, so we use a separate function
_decoded_mode = process_mode_byte(_hex_mode)
# If this entry WAS already in our list, update everything except the stats
# in case this was a re-registration with a different mode, flags, etc.
if _hex_radio_id in self._peers.keys():
self._peers[_hex_radio_id]['IP'] = _ip_address
self._peers[_hex_radio_id]['PORT'] = _port
self._peers[_hex_radio_id]['MODE'] = _hex_mode
self._peers[_hex_radio_id]['MODE_DECODE'] = _decoded_mode
self._peers[_hex_radio_id]['FLAGS'] = ''
self._peers[_hex_radio_id]['FLAGS_DECODE'] = ''
self._logger.debug('(%s) Peer Updated: %s', self._system, self._peers[_hex_radio_id])
# If this entry was NOT already in our list, add it.
if _hex_radio_id not in self._peers.keys():
self._peers[_hex_radio_id] = {
'IP': _ip_address,
'PORT': _port,
'MODE': _hex_mode,
'MODE_DECODE': _decoded_mode,
'FLAGS': '',
'FLAGS_DECODE': '',
'STATUS': {
'CONNECTED': False,
'KEEP_ALIVES_SENT': 0,
'KEEP_ALIVES_MISSED': 0,
'KEEP_ALIVES_OUTSTANDING': 0,
'KEEP_ALIVES_RECEIVED': 0,
'KEEP_ALIVE_RX_TIME': 0
}
}
self._logger.debug('(%s) Peer Added: %s', self._system, self._peers[_hex_radio_id])
# Finally, check to see if there's a peer already in our list that was not in this peer list
# and if so, delete it.
for peer in self._peers.keys():
if peer not in _temp_peers:
self.de_register_peer(peer)
self._logger.warning('(%s) Peer Deleted (not in new peer list): %s', self._system, int_id(peer))
#************************************************
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
#************************************************
def call_mon_status(self, _network, _data):
logger.debug('(%s) Repeater Call Monitor Origin Packet Received: %s',_network, ahex(_data))
def call_mon_status(self, _data):
self._logger.debug('(%s) Repeater Call Monitor Origin Packet Received: %s', self._system, ahex(_data))
def call_mon_rpt(self, _network, _data):
logger.debug('(%s) Repeater Call Monitor Repeating Packet Received: %s', _network, ahex(_data))
def call_mon_rpt(self, _data):
self._logger.debug('(%s) Repeater Call Monitor Repeating Packet Received: %s', self._system, ahex(_data))
def call_mon_nack(self, _network, _data):
logger.debug('(%s) Repeater Call Monitor NACK Packet Received: %s', _network, ahex(_data))
def call_mon_nack(self, _data):
self._logger.debug('(%s) Repeater Call Monitor NACK Packet Received: %s', self._system, ahex(_data))
def xcmp_xnl(self, _network, _data):
logger.debug('(%s) XCMP/XNL Packet Received: %s', _network, ahex(_data))
def xcmp_xnl(self, _data):
self._logger.debug('(%s) XCMP/XNL Packet Received: %s', self._system, ahex(_data))
def repeater_wake_up(self, _network, _data):
logger.debug('(%s) Repeater Wake-Up Packet Received: %s', _network, ahex(_data))
def repeater_wake_up(self, _data):
self._logger.debug('(%s) Repeater Wake-Up Packet Received: %s', self._system, ahex(_data))
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
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_sub))
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
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_sub))
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
logger.debug('(%s) Private Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
self._logger.debug('(%s) Private Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
self._logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
self._logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
def unknown_message(self, _network, _packettype, _peerid, _data):
logger.error('(%s) Unknown Message - Type: %s From: %s Packet: %s', _network, ahex(_packettype), int_id(_peerid), ahex(_data))
def unknown_message(self, _packettype, _peerid, _data):
self._logger.error('(%s) Unknown Message - Type: %s From: %s Packet: %s', ahex(_packettype), self._system, int_id(_peerid), ahex(_data))
#************************************************
@ -457,7 +428,7 @@ class IPSC(DatagramProtocol):
_packet = _packet + _hash
self.transport.write(_packet, (_host, _port))
# USE THE FOLLOWING ONLY UNDER DIRE CIRCUMSTANCES -- PERFORMANCE IS ADVERSLY AFFECTED!
#logger.debug('(%s) TX Packet to %s on port %s: %s', self._system, _host, _port, ahex(_packet))
#self._logger.debug('(%s) TX Packet to %s on port %s: %s', self._system, _host, _port, ahex(_packet))
# Accept a complete packet, ready to be sent, and send it to all active peers + master in an IPSC
#
@ -489,12 +460,12 @@ class IPSC(DatagramProtocol):
self._peers[_peerid]['FLAGS_DECODE'] = _decoded_flags
self.send_packet(self.PEER_ALIVE_REPLY_PKT, (_host, _port))
self.reset_keep_alive(_peerid) # Might as well reset our own counter, we know it's out there...
logger.debug('(%s) Keep-Alive reply sent to Peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
self._logger.debug('(%s) Keep-Alive reply sent to Peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
# SOMEONE WANTS TO REGISTER WITH US - WE'RE COOL WITH THAT
def peer_reg_req(self, _peerid, _host, _port):
self.send_packet(self.PEER_REG_REPLY_PKT, (_host, _port))
logger.info('(%s) Peer Registration Request From: %s, %s:%s', self._system, int_id(_peerid), _host, _port)
self._logger.info('(%s) Peer Registration Request From: %s, %s:%s', self._system, int_id(_peerid), _host, _port)
# SOMEONE HAS ANSWERED OUR KEEP-ALIVE REQUEST - KEEP TRACK OF IT
@ -502,27 +473,27 @@ class IPSC(DatagramProtocol):
self.reset_keep_alive(_peerid)
self._peers[_peerid]['STATUS']['KEEP_ALIVES_RECEIVED'] += 1
self._peers[_peerid]['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time())
logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from Peer %s, %s:%s', self._system, int_id(_peerid), self._peers[_peerid]['IP'], self._peers[_peerid]['PORT'])
self._logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from Peer %s, %s:%s', self._system, int_id(_peerid), self._peers[_peerid]['IP'], self._peers[_peerid]['PORT'])
# SOMEONE HAS ANSWERED OUR REQEST TO REGISTER WITH THEM - KEEP TRACK OF IT
def peer_reg_reply(self, _peerid):
if _peerid in self._peers.keys():
self._peers[_peerid]['STATUS']['CONNECTED'] = True
logger.info('(%s) Registration Reply From: %s, %s:%s', self._system, int_id(_peerid), self._peers[_peerid]['IP'], self._peers[_peerid]['PORT'])
self._logger.info('(%s) Registration Reply From: %s, %s:%s', self._system, int_id(_peerid), self._peers[_peerid]['IP'], self._peers[_peerid]['PORT'])
# OUR MASTER HAS ANSWERED OUR KEEP-ALIVE REQUEST - KEEP TRACK OF IT
def master_alive_reply(self, _peerid):
self.reset_keep_alive(_peerid)
self._master['STATUS']['KEEP_ALIVES_RECEIVED'] += 1
self._master['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time())
logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from the Master %s, %s:%s', self._system, int_id(_peerid), self._master['IP'], self._master['PORT'])
self._logger.debug('(%s) Keep-Alive Reply (we sent the request) Received from the Master %s, %s:%s', self._system, int_id(_peerid), self._master['IP'], self._master['PORT'])
# OUR MASTER HAS SENT US A PEER LIST - PROCESS IT
def peer_list_reply(self, _data, _peerid):
CONFIG['SYSTEMS'][self._system]['MASTER']['STATUS']['PEER_LIST'] = True
self._master['STATUS']['PEER_LIST'] = True
if len(_data) > 18:
process_peer_list(_data, self._system)
logger.debug('(%s) Peer List Reply Received From Master %s, %s:%s', self._system, int_id(_peerid), self._master['IP'], self._master['PORT'])
self.process_peer_list(_data)
self._logger.debug('(%s) Peer List Reply Received From Master %s, %s:%s', self._system, int_id(_peerid), self._master['IP'], self._master['PORT'])
# OUR MASTER HAS ANSWERED OUR REQUEST TO REGISTER - LOTS OF INFORMATION TO TRACK
def master_reg_reply(self, _data, _peerid):
@ -540,7 +511,7 @@ class IPSC(DatagramProtocol):
self._master['FLAGS_DECODE'] = _decoded_flags
self._master_stat['CONNECTED'] = True
self._master_stat['KEEP_ALIVES_OUTSTANDING'] = 0
logger.warning('(%s) Registration response (we requested reg) from the Master: %s, %s:%s (%s peers)', self._system, int_id(_peerid), self._master['IP'], self._master['PORT'], self._local['NUM_PEERS'])
self._logger.warning('(%s) Registration response (we requested reg) from the Master: %s, %s:%s (%s peers)', self._system, int_id(_peerid), self._master['IP'], self._master['PORT'], self._local['NUM_PEERS'])
# WE ARE MASTER AND SOMEONE HAS REQUESTED REGISTRATION FROM US - ANSWER IT
def master_reg_req(self, _data, _peerid, _host, _port):
@ -553,7 +524,7 @@ class IPSC(DatagramProtocol):
self.MASTER_REG_REPLY_PKT = (MASTER_REG_REPLY + self._local_id + self.TS_FLAGS + hex_str_2(self._local['NUM_PEERS']) + IPSC_VER)
self.send_packet(self.MASTER_REG_REPLY_PKT, (_host, _port))
logger.info('(%s) Master Registration Packet Received from peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
self._logger.info('(%s) Master Registration Packet Received from peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
# If this entry was NOT already in our list, add it.
if _peerid not in self._peers.keys():
@ -574,7 +545,7 @@ class IPSC(DatagramProtocol):
}
}
self._local['NUM_PEERS'] = len(self._peers)
logger.debug('(%s) Peer Added To Peer List: %s, %s:%s (IPSC now has %s Peers)', self._system, self._peers[_peerid], _host, _port, self._local['NUM_PEERS'])
self._logger.debug('(%s) Peer Added To Peer List: %s, %s:%s (IPSC now has %s Peers)', self._system, self._peers[_peerid], _host, _port, self._local['NUM_PEERS'])
# WE ARE MASTER AND SOEMONE SENT US A KEEP-ALIVE - ANSWER IT, TRACK IT
def master_alive_req(self, _peerid, _host, _port):
@ -582,17 +553,17 @@ class IPSC(DatagramProtocol):
self._peers[_peerid]['STATUS']['KEEP_ALIVES_RECEIVED'] += 1
self._peers[_peerid]['STATUS']['KEEP_ALIVE_RX_TIME'] = int(time())
self.send_packet(self.MASTER_ALIVE_REPLY_PKT, (_host, _port))
logger.debug('(%s) Master Keep-Alive Request Received from peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
self._logger.debug('(%s) Master Keep-Alive Request Received from peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
else:
logger.warning('(%s) Master Keep-Alive Request Received from *UNREGISTERED* peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
self._logger.warning('(%s) Master Keep-Alive Request Received from *UNREGISTERED* peer %s, %s:%s', self._system, int_id(_peerid), _host, _port)
# WE ARE MASTER AND A PEER HAS REQUESTED A PEER LIST - SEND THEM ONE
def peer_list_req(self, _peerid):
if _peerid in self._peers.keys():
logger.debug('(%s) Peer List Request from peer %s', self._system, int_id(_peerid))
self._logger.debug('(%s) Peer List Request from peer %s', self._system, int_id(_peerid))
self.send_to_ipsc(self.PEER_LIST_REPLY_PKT + build_peer_list(self._peers))
else:
logger.warning('(%s) Peer List Request Received from *UNREGISTERED* peer %s', self._system, int_id(_peerid))
self._logger.warning('(%s) Peer List Request Received from *UNREGISTERED* peer %s', self._system, int_id(_peerid))
# Reset the outstanding keep-alive counter for _peerid...
@ -661,45 +632,45 @@ class IPSC(DatagramProtocol):
# Timed loop used for IPSC connection Maintenance when we are the MASTER
#
def master_maintenance_loop(self):
logger.debug('(%s) MASTER Connection Maintenance Loop Started', self._system)
self._logger.debug('(%s) MASTER Connection Maintenance Loop Started', self._system)
update_time = int(time())
for peer in self._peers.keys():
keep_alive_delta = update_time - self._peers[peer]['STATUS']['KEEP_ALIVE_RX_TIME']
logger.debug('(%s) Time Since Last KeepAlive Request from Peer %s: %s seconds', self._system, int_id(peer), keep_alive_delta)
self._logger.debug('(%s) Time Since Last KeepAlive Request from Peer %s: %s seconds', self._system, int_id(peer), keep_alive_delta)
if keep_alive_delta > 120:
de_register_peer(self._system, peer)
self.de_register_peer(peer)
self.send_to_ipsc(self.PEER_LIST_REPLY_PKT + build_peer_list(self._peers))
logger.warning('(%s) Timeout Exceeded for Peer %s, De-registering', self._system, int_id(peer))
self._logger.warning('(%s) Timeout Exceeded for Peer %s, De-registering', self._system, int_id(peer))
# Timed loop used for IPSC connection Maintenance when we are a PEER
#
def peer_maintenance_loop(self):
logger.debug('(%s) PEER Connection Maintenance Loop Started', self._system)
self._logger.debug('(%s) PEER Connection Maintenance Loop Started', self._system)
# If the master isn't connected, we have to do that before we can do anything else!
#
if not self._master_stat['CONNECTED']:
self.send_packet(self.MASTER_REG_REQ_PKT, self._master_sock)
logger.info('(%s) Registering with the Master: %s:%s', self._system, self._master['IP'], self._master['PORT'])
self._logger.info('(%s) Registering with the Master: %s:%s', self._system, self._master['IP'], self._master['PORT'])
# Once the master is connected, we have to send keep-alives.. and make sure we get them back
elif self._master_stat['CONNECTED']:
# Send keep-alive to the master
self.send_packet(self.MASTER_ALIVE_PKT, self._master_sock)
logger.debug('(%s) Keep Alive Sent to the Master: %s, %s:%s', self._system, int_id(self._master['RADIO_ID']) ,self._master['IP'], self._master['PORT'])
self._logger.debug('(%s) Keep Alive Sent to the Master: %s, %s:%s', self._system, int_id(self._master['RADIO_ID']) ,self._master['IP'], self._master['PORT'])
# If we had a keep-alive outstanding by the time we send another, mark it missed.
if (self._master_stat['KEEP_ALIVES_OUTSTANDING']) > 0:
self._master_stat['KEEP_ALIVES_MISSED'] += 1
logger.info('(%s) Master Keep-Alive Missed: %s:%s', self._system, self._master['IP'], self._master['PORT'])
self._logger.info('(%s) Master Keep-Alive Missed: %s:%s', self._system, self._master['IP'], self._master['PORT'])
# If we have missed too many keep-alives, de-register the master and start over.
if self._master_stat['KEEP_ALIVES_OUTSTANDING'] >= self._local['MAX_MISSED']:
self._master_stat['CONNECTED'] = False
self._master_stat['KEEP_ALIVES_OUTSTANDING'] = 0
logger.error('(%s) Maximum Master Keep-Alives Missed -- De-registering the Master: %s:%s', self._system, self._master['IP'], self._master['PORT'])
self._logger.error('(%s) Maximum Master Keep-Alives Missed -- De-registering the Master: %s:%s', self._system, self._master['IP'], self._master['PORT'])
# Update our stats before we move on...
self._master_stat['KEEP_ALIVES_SENT'] += 1
@ -707,7 +678,7 @@ class IPSC(DatagramProtocol):
else:
# This is bad. If we get this message, we need to reset the state and try again
logger.error('->> (%s) Master in UNKOWN STATE: %s:%s', self._system, self._master_sock)
self._logger.error('->> (%s) Master in UNKOWN STATE: %s:%s', self._system, self._master_sock)
self._master_stat['CONNECTED'] = False
@ -717,10 +688,10 @@ class IPSC(DatagramProtocol):
# Ask the master for a peer-list
if self._local['NUM_PEERS']:
self.send_packet(self.PEER_LIST_REQ_PKT, self._master_sock)
logger.info('(%s), No Peer List - Requesting One From the Master', self._system)
self._logger.info('(%s), No Peer List - Requesting One From the Master', self._system)
else:
self._master_stat['PEER_LIST'] = True
logger.debug('(%s), Skip asking for a Peer List, we are the only Peer', self._system)
self._logger.debug('(%s), Skip asking for a Peer List, we are the only Peer', self._system)
# If we do have a peer-list, we need to register with the peers and send keep-alives...
@ -736,23 +707,23 @@ class IPSC(DatagramProtocol):
# If we haven't registered to a peer, send a registration
if not self._peers[peer]['STATUS']['CONNECTED']:
self.send_packet(self.PEER_REG_REQ_PKT, (self._peers[peer]['IP'], self._peers[peer]['PORT']))
logger.info('(%s) Registering with Peer %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
self._logger.info('(%s) Registering with Peer %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
# If we have registered with the peer, then send a keep-alive
elif self._peers[peer]['STATUS']['CONNECTED']:
self.send_packet(self.PEER_ALIVE_REQ_PKT, (self._peers[peer]['IP'], self._peers[peer]['PORT']))
logger.debug('(%s) Keep-Alive Sent to the Peer %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
self._logger.debug('(%s) Keep-Alive Sent to the Peer %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
# If we have a keep-alive outstanding by the time we send another, mark it missed.
if self._peers[peer]['STATUS']['KEEP_ALIVES_OUTSTANDING'] > 0:
self._peers[peer]['STATUS']['KEEP_ALIVES_MISSED'] += 1
logger.info('(%s) Peer Keep-Alive Missed for %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
self._logger.info('(%s) Peer Keep-Alive Missed for %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
# If we have missed too many keep-alives, de-register the peer and start over.
if self._peers[peer]['STATUS']['KEEP_ALIVES_OUTSTANDING'] >= self._local['MAX_MISSED']:
self._peers[peer]['STATUS']['CONNECTED'] = False
#del peer # Becuase once it's out of the dictionary, you can't use it for anything else.
logger.warning('(%s) Maximum Peer Keep-Alives Missed -- De-registering the Peer: %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
self._logger.warning('(%s) Maximum Peer Keep-Alives Missed -- De-registering the Peer: %s, %s:%s', self._system, int_id(peer), self._peers[peer]['IP'], self._peers[peer]['PORT'])
# Update our stats before moving on...
self._peers[peer]['STATUS']['KEEP_ALIVES_SENT'] += 1
@ -781,7 +752,7 @@ class IPSC(DatagramProtocol):
# AUTHENTICATE THE PACKET
if self._local['AUTH_ENABLED']:
if not self.validate_auth(self._local['AUTH_KEY'], data):
logger.warning('(%s) AuthError: IPSC packet failed authentication. Type %s: Peer: %s, %s:%s', self._system, ahex(_packettype), int_id(_peerid), host, port)
self._logger.warning('(%s) AuthError: IPSC packet failed authentication. Type %s: Peer: %s, %s:%s', self._system, ahex(_packettype), int_id(_peerid), host, port)
return
# REMOVE SHA-1 AUTHENTICATION HASH: WE NO LONGER NEED IT
@ -790,8 +761,8 @@ class IPSC(DatagramProtocol):
# PACKETS THAT WE RECEIVE FROM ANY VALID PEER OR VALID MASTER
if _packettype in ANY_PEER_REQUIRED:
if not(valid_master(self._system, _peerid) == False or valid_peer(self._peers.keys(), _peerid) == False):
logger.warning('(%s) PeerError: Peer not in peer-list: %s, %s:%s', self._system, int_id(_peerid), host, port)
if not(self.valid_master(_peerid) == False or valid_peer(self._peers.keys(), _peerid) == False):
self._logger.warning('(%s) PeerError: Peer not in peer-list: %s, %s:%s', self._system, int_id(_peerid), host, port)
return
# ORIGINATED BY SUBSCRIBER UNITS - a.k.a someone transmitted
@ -802,7 +773,7 @@ class IPSC(DatagramProtocol):
_call_type = data[12:13]
_unknown_1 = data[13:17]
_call_info = int_id(data[17:18])
_ts = bool(_call_info & TS_CALL_MSK)
_ts = bool(_call_info & TS_CALL_MSK) + 1
_end = bool(_call_info & END_MSK)
# Extract RTP Header Fields
@ -823,59 +794,59 @@ class IPSC(DatagramProtocol):
# User Voice and Data Call Types:
if _packettype == GROUP_VOICE:
self.reset_keep_alive(_peerid)
self.group_voice(self._system, _src_sub, _dst_sub, _ts, _end, _peerid, data)
self.group_voice(_src_sub, _dst_sub, _ts, _end, _peerid, data)
return
elif _packettype == PVT_VOICE:
self.reset_keep_alive(_peerid)
self.private_voice(self._system, _src_sub, _dst_sub, _ts, _end, _peerid, data)
self.private_voice(_src_sub, _dst_sub, _ts, _end, _peerid, data)
return
elif _packettype == GROUP_DATA:
self.reset_keep_alive(_peerid)
self.group_data(self._system, _src_sub, _dst_sub, _ts, _end, _peerid, data)
self.group_data(_src_sub, _dst_sub, _ts, _end, _peerid, data)
return
elif _packettype == PVT_DATA:
self.reset_keep_alive(_peerid)
self.private_data(self._system, _src_sub, _dst_sub, _ts, _end, _peerid, data)
self.private_data(_src_sub, _dst_sub, _ts, _end, _peerid, data)
return
return
# MOTOROLA XCMP/XNL CONTROL PROTOCOL: We don't process these (yet)
elif _packettype == XCMP_XNL:
self.xcmp_xnl(self._system, data)
self.xcmp_xnl(data)
return
# ORIGINATED BY PEERS, NOT IPSC MAINTENANCE: Call monitoring is all we've found here so far
elif _packettype == CALL_MON_STATUS:
self.call_mon_status(self._system, data)
self.call_mon_status(data)
return
elif _packettype == CALL_MON_RPT:
self.call_mon_rpt(self._system, data)
self.call_mon_rpt(data)
return
elif _packettype == CALL_MON_NACK:
self.call_mon_nack(self._system, data)
self.call_mon_nack(data)
return
# IPSC CONNECTION MAINTENANCE MESSAGES
elif _packettype == DE_REG_REQ:
de_register_peer(self._system, _peerid)
logger.warning('(%s) Peer De-Registration Request From: %s, %s:%s', self._system, int_id(_peerid), host, port)
self.de_register_peer(_peerid)
self._logger.warning('(%s) Peer De-Registration Request From: %s, %s:%s', self._system, int_id(_peerid), host, port)
return
elif _packettype == DE_REG_REPLY:
logger.warning('(%s) Peer De-Registration Reply From: %s, %s:%s', self._system, int_id(_peerid), host, port)
self._logger.warning('(%s) Peer De-Registration Reply From: %s, %s:%s', self._system, int_id(_peerid), host, port)
return
elif _packettype == RPT_WAKE_UP:
self.repeater_wake_up(self._system, data)
logger.debug('(%s) Repeater Wake-Up Packet From: %s, %s:%s', self._system, int_id(_peerid), host, port)
self.repeater_wake_up(data)
self._logger.debug('(%s) Repeater Wake-Up Packet From: %s, %s:%s', self._system, int_id(_peerid), host, port)
return
return
@ -884,8 +855,8 @@ class IPSC(DatagramProtocol):
# ONLY ACCEPT FROM A PREVIOUSLY VALIDATED PEER
if _packettype in PEER_REQUIRED:
if not valid_peer(self._peers.keys(), _peerid):
logger.warning('(%s) PeerError: Peer not in peer-list: %s, %s:%s', self._system, int_id(_peerid), host, port)
if not self.valid_peer(_peerid):
self._logger.warning('(%s) PeerError: Peer not in peer-list: %s, %s:%s', self._system, int_id(_peerid), host, port)
return
# REQUESTS FROM PEERS: WE MUST REPLY IMMEDIATELY FOR IPSC MAINTENANCE
@ -912,8 +883,8 @@ class IPSC(DatagramProtocol):
# PACKETS WE ONLY ACCEPT IF WE HAVE FINISHED REGISTERING WITH OUR MASTER
if _packettype in MASTER_REQUIRED:
if not valid_master(self._system, _peerid):
logger.warning('(%s) MasterError: %s, %s:%s is not the master peer', self._system, int_id(_peerid), host, port)
if not self.valid_master(_peerid):
self._logger.warning('(%s) MasterError: %s, %s:%s is not the master peer', self._system, int_id(_peerid), host, port)
return
# ANSWERS FROM REQUESTS WE SENT TO THE MASTER: WE DO NOT REPLY
@ -1008,8 +979,8 @@ if __name__ == '__main__':
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
if CONFIG['REPORTS']['REPORT_NETWORKS']:
config_reporting_loop(CONFIG['REPORTS']['REPORT_NETWORKS'])
reporting = task.LoopingCall(reporting_loop)
reporting_loop = config_reports(CONFIG)
reporting = task.LoopingCall(reporting_loop, logger)
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
reactor.run()

View File

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

View File

@ -41,6 +41,7 @@ def build_config(_config_file):
CONFIG['GLOBAL'] = {}
CONFIG['REPORTS'] = {}
CONFIG['LOGGER'] = {}
CONFIG['ALIASES'] = {}
CONFIG['SYSTEMS'] = {}
try:
@ -67,6 +68,18 @@ def build_config(_config_file):
'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': {}}})

129
log.py
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

115
rcm.py
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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