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
+2 -2
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
#
+103 -32
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()
+54 -54
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
+210 -239
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()
+17
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.
+13
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': {}}})
+96 -33
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()
+2930 -1789
View File
File diff suppressed because it is too large Load Diff
+69 -30
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()
+74 -24
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()
+91 -24
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()
+62 -18
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()
+49387 -26346
View File
File diff suppressed because it is too large Load Diff
+1 -17
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
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