Merge branch 'modularization'
This commit is contained in:
commit
8dbba4b42d
|
@ -12,8 +12,8 @@ gateway = 127.0.0.1 # IP address of DMRGateway app
|
|||
toGatewayPort = 31000 # Port DMRGateway is listening on for AMBE frames to decode
|
||||
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
|
||||
#
|
||||
|
|
135
ambe_audio.py
135
ambe_audio.py
|
@ -28,7 +28,10 @@ from bitstring import BitArray
|
|||
|
||||
import sys, socket, ConfigParser, thread, traceback
|
||||
import cPickle as pickle
|
||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3, hex_str_4, get_info, talkgroup_ids, peer_ids, PATH, get_subscriber_info, reread_subscribers
|
||||
|
||||
from dmrlink import IPSC, systems
|
||||
from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias
|
||||
|
||||
from time import time, sleep, clock, localtime, strftime
|
||||
import csv
|
||||
import struct
|
||||
|
@ -90,8 +93,8 @@ class ambeIPSC(IPSC):
|
|||
#_d = None
|
||||
###### DEBUGDEBUGDEBUG
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
def __init__(self, _name, _config, _logger):
|
||||
IPSC.__init__(self, _name, _config, _logger)
|
||||
self.CALL_DATA = []
|
||||
|
||||
#
|
||||
|
@ -99,7 +102,7 @@ class ambeIPSC(IPSC):
|
|||
#
|
||||
|
||||
self._currentTG = self._no_tg
|
||||
self._currentNetwork = str(args[0])
|
||||
self._currentNetwork = str(_name)
|
||||
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
||||
|
||||
logger.info('DMRLink ambe server')
|
||||
|
@ -121,7 +124,7 @@ class ambeIPSC(IPSC):
|
|||
|
||||
try:
|
||||
thread.start_new_thread( self.remote_control, (self._remote_control_port, ) ) # Listen for remote control commands
|
||||
thread.start_new_thread( self.launchUDP, (args[0], ) ) # Package AMBE into IPSC frames and send to all peers
|
||||
thread.start_new_thread( self.launchUDP, (_name, ) ) # Package AMBE into IPSC frames and send to all peers
|
||||
except:
|
||||
traceback.print_exc()
|
||||
logger.error( "Error: unable to start thread" )
|
||||
|
@ -178,7 +181,7 @@ class ambeIPSC(IPSC):
|
|||
traceback.print_exc()
|
||||
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
||||
|
||||
def rewriteFrame( self, _frame, _network, _newSlot, _newGroup, _newSouceID, _newPeerID ):
|
||||
def rewriteFrame( self, _frame, _newSlot, _newGroup, _newSouceID, _newPeerID ):
|
||||
|
||||
_peerid = _frame[1:5] # int32 peer who is sending us a packet
|
||||
_src_sub = _frame[6:9] # int32 Id of source
|
||||
|
@ -266,7 +269,7 @@ class ambeIPSC(IPSC):
|
|||
return _ambeAll.tobytes() # Return the 49 * 3 as an array of bytes
|
||||
|
||||
# Set up the socket and run the method to gather the AMBE. Sending it to all peers
|
||||
def launchUDP(self, _network):
|
||||
def launchUDP(self):
|
||||
s = socket.socket() # Create a socket object
|
||||
s.bind(('', self._ambeRxPort)) # Bind to the port
|
||||
|
||||
|
@ -274,16 +277,16 @@ class ambeIPSC(IPSC):
|
|||
s.listen(5) # Now wait for client connection.
|
||||
_sock, addr = s.accept() # Establish connection with client.
|
||||
if int_id(self._tx_tg) > 0: # Test if we are allowed to transmit
|
||||
self.playbackFromUDP(_sock, _network)
|
||||
self.playbackFromUDP(_sock, self._system)
|
||||
else:
|
||||
self.transmitDisabled(_sock, _network) #tg is zero, so just eat the network trafic
|
||||
self.transmitDisabled(_sock, self._system) #tg is zero, so just eat the network trafic
|
||||
_sock.close()
|
||||
|
||||
# This represents a full transmission (HEAD, VOICE and TERM)
|
||||
def playbackFromUDP(self, _sock, _network):
|
||||
def playbackFromUDP(self, _sock):
|
||||
_delay = 0.055 # Yes, I know it should be 0.06, but there seems to be some latency, so this is a hack
|
||||
_src_sub = hex_str_3(self._gateway_dmr_id) # DMR ID to sign this transmission with
|
||||
_src_peer = NETWORK[_network]['LOCAL']['RADIO_ID'] # Use this peers ID as the source repeater
|
||||
_src_peer = NETWORK[self._system]['LOCAL']['RADIO_ID'] # Use this peers ID as the source repeater
|
||||
|
||||
logger.info('Transmit from gateway to TG {}:'.format(int_id(self._tx_tg)) )
|
||||
try:
|
||||
|
@ -310,8 +313,8 @@ class ambeIPSC(IPSC):
|
|||
self._seq = randint(0,32767) # A transmission uses a random number to begin its sequence (16 bit)
|
||||
|
||||
for i in range(0, 3): # Output the 3 HEAD frames to our peers
|
||||
self.rewriteFrame(_tempHead[i], _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
|
||||
self.rewriteFrame(_tempHead[i], self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||
#self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempHead[i])
|
||||
sleep(_delay)
|
||||
|
||||
i = 0 # Initialize the VOICE template index
|
||||
|
@ -321,21 +324,21 @@ class ambeIPSC(IPSC):
|
|||
i = (i + 1) % 6 # Round robbin with the 6 VOICE templates
|
||||
_frame = _tempVoice[i][:33] + _ambe + _tempVoice[i][52:] # Insert the 3 49 bit AMBE frames
|
||||
|
||||
self.rewriteFrame(_frame, _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame)
|
||||
self.rewriteFrame(_frame, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||
#self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _frame)
|
||||
|
||||
sleep(_delay) # Since this comes from a file we have to add delay between IPSC frames
|
||||
else:
|
||||
_eof = True # There are no more AMBE frames, so terminate the loop
|
||||
|
||||
self.rewriteFrame(_tempTerm, _network, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||
#self.group_voice(_network, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
|
||||
self.rewriteFrame(_tempTerm, self._system, self._tx_ts, self._tx_tg, _src_sub, _src_peer)
|
||||
#self.group_voice(self._system, _src_sub, self._tx_tg, True, '', hex_str_3(0), _tempTerm)
|
||||
|
||||
except IOError:
|
||||
logger.error('Can not transmit to peers')
|
||||
logger.info('Transmit complete')
|
||||
|
||||
def transmitDisabled(self, _sock, _network):
|
||||
def transmitDisabled(self, _sock):
|
||||
_eof = False
|
||||
logger.debug('Transmit disabled begin')
|
||||
while(_eof == False):
|
||||
|
@ -386,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()
|
||||
|
|
332
bridge.py
332
bridge.py
|
@ -18,18 +18,18 @@
|
|||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
###############################################################################
|
||||
|
||||
# This is a sample application to bridge traffic between IPSC networks. it uses
|
||||
# This is a sample application to bridge traffic between IPSC systems. it uses
|
||||
# one required (bridge_rules.py) and one optional (known_bridges.py) additional
|
||||
# configuration files. Both files have their own documentation for use.
|
||||
#
|
||||
# "bridge_rules" contains the IPSC network, Timeslot and TGID matching rules to
|
||||
# determine which voice calls are bridged between IPSC networks and which are
|
||||
# determine which voice calls are bridged between IPSC systems and which are
|
||||
# not.
|
||||
#
|
||||
# "known_bridges" contains DMR radio ID numbers of known bridges. This file is
|
||||
# used when you want bridge.py to be "polite" or serve as a backup bridge. If
|
||||
# a known bridge exists in either a source OR target IPSC network, then no
|
||||
# bridging between those IPSC networks will take place. This behavior is
|
||||
# bridging between those IPSC systems will take place. This behavior is
|
||||
# dynamic and updates each keep-alive interval (main configuration file).
|
||||
# For faster failover, configure a short keep-alive time and a low number of
|
||||
# missed keep-alives before timout. I recommend 5 sec keep-alive and 3 missed.
|
||||
|
@ -46,14 +46,17 @@
|
|||
from __future__ import print_function
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet import task
|
||||
from binascii import b2a_hex as h
|
||||
from binascii import b2a_hex as ahex
|
||||
from time import time
|
||||
|
||||
# For debugging
|
||||
from pprint import pprint
|
||||
from importlib import import_module
|
||||
|
||||
import sys
|
||||
from dmrlink import IPSC, NETWORK, networks, REPORTS, reporting_loop, dmr_nat, logger, hex_str_3, hex_str_4, int_id
|
||||
|
||||
from dmr_utils.utils import hex_str_3, hex_str_4, int_id
|
||||
|
||||
from dmrlink import IPSC, systems, config_reports
|
||||
from ipsc.ipsc_const import BURST_DATA_TYPE
|
||||
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
|
@ -63,15 +66,6 @@ __maintainer__ = 'Cort Buffington, N0MJS'
|
|||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
|
||||
# Constants for this application
|
||||
#
|
||||
BURST_DATA_TYPE = {
|
||||
'VOICE_HEAD': '\x01',
|
||||
'VOICE_TERM': '\x02',
|
||||
'SLOT1_VOICE': '\x0A',
|
||||
'SLOT2_VOICE': '\x8A'
|
||||
}
|
||||
|
||||
# Minimum time between different subscribers transmitting on the same TGID
|
||||
#
|
||||
TS_CLEAR_TIME = .2
|
||||
|
@ -81,79 +75,87 @@ TS_CLEAR_TIME = .2
|
|||
# configuration file and listed as "active". It can be empty,
|
||||
# but it has to exist.
|
||||
#
|
||||
try:
|
||||
from bridge_rules import RULES as RULES_FILE
|
||||
logger.info('Bridge rules file found and rules imported')
|
||||
except ImportError:
|
||||
sys.exit('Bridging rules file not found or invalid')
|
||||
def build_rules(_bridge_rules):
|
||||
try:
|
||||
rule_file = import_module(_bridge_rules)
|
||||
logger.info('Bridge rules file found and rules imported')
|
||||
except ImportError:
|
||||
sys.exit('Bridging rules file not found or invalid')
|
||||
|
||||
# Convert integer GROUP ID numbers from the config into hex strings
|
||||
# we need to send in the actual data packets.
|
||||
#
|
||||
# Convert integer GROUP ID numbers from the config into hex strings
|
||||
# we need to send in the actual data packets.
|
||||
#
|
||||
|
||||
for _ipsc in RULES_FILE:
|
||||
for _rule in RULES_FILE[_ipsc]['GROUP_VOICE']:
|
||||
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
|
||||
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
|
||||
_rule['SRC_TS'] = _rule['SRC_TS']
|
||||
_rule['DST_TS'] = _rule['DST_TS']
|
||||
for i, e in enumerate(_rule['ON']):
|
||||
_rule['ON'][i] = hex_str_3(_rule['ON'][i])
|
||||
for i, e in enumerate(_rule['OFF']):
|
||||
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
||||
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
||||
_rule['TIMER'] = time() + _rule['TIMEOUT']
|
||||
if _ipsc not in NETWORK:
|
||||
sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
|
||||
for _ipsc in NETWORK:
|
||||
if _ipsc not in RULES_FILE:
|
||||
sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
|
||||
for _ipsc in rule_file.RULES:
|
||||
for _rule in rule_file.RULES[_ipsc]['GROUP_VOICE']:
|
||||
_rule['SRC_GROUP'] = hex_str_3(_rule['SRC_GROUP'])
|
||||
_rule['DST_GROUP'] = hex_str_3(_rule['DST_GROUP'])
|
||||
_rule['SRC_TS'] = _rule['SRC_TS']
|
||||
_rule['DST_TS'] = _rule['DST_TS']
|
||||
for i, e in enumerate(_rule['ON']):
|
||||
_rule['ON'][i] = hex_str_3(_rule['ON'][i])
|
||||
for i, e in enumerate(_rule['OFF']):
|
||||
_rule['OFF'][i] = hex_str_3(_rule['OFF'][i])
|
||||
_rule['TIMEOUT']= _rule['TIMEOUT']*60
|
||||
_rule['TIMER'] = time() + _rule['TIMEOUT']
|
||||
if _ipsc not in CONFIG['SYSTEMS']:
|
||||
sys.exit('ERROR: Bridge rules found for an IPSC network not configured in main configuration')
|
||||
for _ipsc in CONFIG['SYSTEMS']:
|
||||
if _ipsc not in rule_file.RULES:
|
||||
sys.exit('ERROR: Bridge rules not found for all IPSC network configured')
|
||||
|
||||
RULES = RULES_FILE
|
||||
return rule_file.RULES
|
||||
|
||||
# Import List of Bridges
|
||||
# This is how we identify known bridges. If one of these is present
|
||||
# and it's mode byte is set to bridge, we don't
|
||||
#
|
||||
try:
|
||||
from known_bridges import BRIDGES
|
||||
logger.info('Known bridges file found and bridge ID list imported ')
|
||||
except ImportError:
|
||||
logger.critical('\'known_bridges.py\' not found - backup bridge service will not be enabled')
|
||||
BRIDGES = []
|
||||
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')
|
||||
return []
|
||||
|
||||
|
||||
# Import subscriber ACL
|
||||
# ACL may be a single list of subscriber IDs
|
||||
# Global action is to allow or deny them. Multiple lists with different actions and ranges
|
||||
# are not yet implemented.
|
||||
try:
|
||||
from sub_acl import ACL_ACTION, ACL
|
||||
# uses more memory to build hex strings, but processes MUCH faster when checking for matches
|
||||
for i, e in enumerate(ACL):
|
||||
ACL[i] = hex_str_3(ACL[i])
|
||||
logger.info('Subscriber access control file found, subscriber ACL imported')
|
||||
except ImportError:
|
||||
logger.critical('\'sub_acl.py\' not found - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
def build_acl(_sub_acl):
|
||||
try:
|
||||
acl_file = import_module(_sub_acl)
|
||||
for i, e in enumerate(acl_file.ACL):
|
||||
acl_file.ACL[i] = hex_str_3(acl_file.ACL[i])
|
||||
logger.info('ACL file found and ACL entries imported')
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
|
||||
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
||||
# define a differnet function to be used to check the ACL
|
||||
if ACL_ACTION == 'PERMIT':
|
||||
def allow_sub(_sub):
|
||||
if _sub in ACL:
|
||||
# Depending on which type of ACL is used (PERMIT, DENY... or there isn't one)
|
||||
# define a differnet function to be used to check the ACL
|
||||
global allow_sub
|
||||
if acl_file.ACL_ACTION == 'PERMIT':
|
||||
def allow_sub(_sub):
|
||||
if _sub in ACL:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
elif acl_file.ACL_ACTION == 'DENY':
|
||||
def allow_sub(_sub):
|
||||
if _sub not in ACL:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
def allow_sub(_sub):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
elif ACL_ACTION == 'DENY':
|
||||
def allow_sub(_sub):
|
||||
if _sub not in ACL:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
def allow_sub(_sub):
|
||||
return True
|
||||
|
||||
|
||||
return acl_file.ACL
|
||||
|
||||
|
||||
# Run this every minute for rule timer updates
|
||||
def rule_timer_loop():
|
||||
logger.debug('(ALL IPSC) Rule timer loop started')
|
||||
|
@ -179,15 +181,17 @@ def rule_timer_loop():
|
|||
else:
|
||||
logger.debug('Rule timer loop made no rule changes')
|
||||
|
||||
|
||||
class bridgeIPSC(IPSC):
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
if BRIDGES:
|
||||
logger.info('Initializing backup/polite bridging')
|
||||
def __init__(self, _name, _config, _logger, _bridges):
|
||||
IPSC.__init__(self, _name, _config, _logger)
|
||||
self.BRIDGES = _bridges
|
||||
if self.BRIDGES:
|
||||
self._logger.info('(%s) Initializing backup/polite bridging', self._system)
|
||||
self.BRIDGE = False
|
||||
else:
|
||||
self.BRIDGE = True
|
||||
logger.info('Initializing standard bridging')
|
||||
self._logger.info('Initializing standard bridging')
|
||||
|
||||
self.IPSC_STATUS = {
|
||||
1: {'RX_GROUP':'\x00', 'TX_GROUP':'\x00', 'RX_TIME':0, 'TX_TIME':0, 'RX_SRC_SUB':'\x00', 'TX_SRC_SUB':'\x00'},
|
||||
|
@ -199,31 +203,32 @@ class bridgeIPSC(IPSC):
|
|||
|
||||
# Setup the backup/polite bridging maintenance loop (based on keep-alive timer)
|
||||
|
||||
if BRIDGES:
|
||||
def startProtocol(self):
|
||||
IPSC.startProtocol(self)
|
||||
|
||||
|
||||
def startProtocol(self):
|
||||
IPSC.startProtocol(self)
|
||||
if self.BRIDGES:
|
||||
self._bridge_presence = task.LoopingCall(self.bridge_presence_loop)
|
||||
self._bridge_presence_loop = self._bridge_presence.start(self._local['ALIVE_TIMER'])
|
||||
|
||||
# This is the backup/polite bridge maintenance loop
|
||||
def bridge_presence_loop(self):
|
||||
self._logger.debug('(%s) Bridge presence loop initiated', self._system)
|
||||
_temp_bridge = True
|
||||
for peer in BRIDGES:
|
||||
for peer in self.BRIDGES:
|
||||
_peer = hex_str_4(peer)
|
||||
|
||||
if _peer in self._peers.keys() and (self._peers[_peer]['MODE_DECODE']['TS_1'] or self._peers[_peer]['MODE_DECODE']['TS_2']):
|
||||
_temp_bridge = False
|
||||
logger.debug('(%s) Peer %s is an active bridge', self._network, int_id(_peer))
|
||||
self._logger.debug('(%s) Peer %s is an active bridge', self._system, int_id(_peer))
|
||||
|
||||
if _peer == self._master['RADIO_ID'] \
|
||||
and self._master['STATUS']['CONNECTED'] \
|
||||
and (self._master['MODE_DECODE']['TS_1'] or self._master['MODE_DECODE']['TS_2']):
|
||||
_temp_bridge = False
|
||||
logger.debug('(%s) Master %s is an active bridge',self._network, int_id(_peer))
|
||||
self._logger.debug('(%s) Master %s is an active bridge',self._system, int_id(_peer))
|
||||
|
||||
if self.BRIDGE != _temp_bridge:
|
||||
logger.info('(%s) Changing bridge status to: %s', self._network, _temp_bridge )
|
||||
self._logger.info('(%s) Changing bridge status to: %s', self._system, _temp_bridge )
|
||||
self.BRIDGE = _temp_bridge
|
||||
|
||||
|
||||
|
@ -231,32 +236,31 @@ class bridgeIPSC(IPSC):
|
|||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
#
|
||||
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||
def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||
# Check for ACL match, and return if the subscriber is not allowed
|
||||
if allow_sub(_src_sub) == False:
|
||||
logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||
self._logger.warning('(%s) Group Voice Packet ***REJECTED BY ACL*** From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||
return
|
||||
|
||||
# Process the packet
|
||||
logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||
self._logger.debug('(%s) Group Voice Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_group))
|
||||
_burst_data_type = _data[30] # Determine the type of voice packet this is (see top of file for possible types)
|
||||
_seq_id = _data[5]
|
||||
_ts += 1
|
||||
|
||||
now = time() # Mark packet arrival time -- we'll need this for call contention handling
|
||||
|
||||
for rule in RULES[_network]['GROUP_VOICE']:
|
||||
for rule in RULES[self._system]['GROUP_VOICE']:
|
||||
_target = rule['DST_NET'] # Shorthand to reduce length and make it easier to read
|
||||
_status = networks[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
|
||||
|
||||
_status = systems[_target].IPSC_STATUS # Shorthand to reduce length and make it easier to read
|
||||
|
||||
# This is the primary rule match to determine if the call will be routed.
|
||||
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or networks[_target].BRIDGE == True):
|
||||
if (rule['SRC_GROUP'] == _dst_group and rule['SRC_TS'] == _ts and rule['ACTIVE'] == True) and (self.BRIDGE == True or systems[_target].BRIDGE == True):
|
||||
|
||||
#
|
||||
# BEGIN CONTENTION HANDLING
|
||||
#
|
||||
# If this is an inter-DMRlink trunk, this isn't necessary
|
||||
if RULES[_network]['TRUNK'] == False:
|
||||
if RULES[self._system]['TRUNK'] == False:
|
||||
|
||||
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
|
||||
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime
|
||||
|
@ -265,27 +269,26 @@ class bridgeIPSC(IPSC):
|
|||
# From the same group as the last TX to this IPSC, but from a different subscriber, and it has been less than TS Clear Time
|
||||
# The "continue" at the end of each means the next iteration of the for loop that tests for matching rules
|
||||
#
|
||||
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[_network]['GROUP_HANGTIME'])):
|
||||
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
||||
self._logger.info('(%s) Call not bridged to TGID%s, target active or in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
||||
continue
|
||||
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[_network]['GROUP_HANGTIME'])):
|
||||
if ((rule['DST_GROUP'] != _status[rule['DST_TS']]['TX_GROUP']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < RULES[self._system]['GROUP_HANGTIME'])):
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
|
||||
self._logger.info('(%s) Call not bridged to TGID%s, target in group hangtime: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']))
|
||||
continue
|
||||
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['RX_GROUP']) and ((now - _status[rule['DST_TS']]['RX_TIME']) < TS_CLEAR_TIME):
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', _network, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
||||
self._logger.info('(%s) Call not bridged to TGID%s, matching call already active on target: IPSC: %s, TS: %s, TGID: %s', self._system, int_id(rule['DST_GROUP']), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['RX_GROUP']))
|
||||
continue
|
||||
if (rule['DST_GROUP'] == _status[rule['DST_TS']]['TX_GROUP']) and (_src_sub != _status[rule['DST_TS']]['TX_SRC_SUB']) and ((now - _status[rule['DST_TS']]['TX_TIME']) < TS_CLEAR_TIME):
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', _network, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
|
||||
self._logger.info('(%s) Call not bridged for subscriber %s, call bridge in progress on target: IPSC: %s, TS: %s, TGID: %s SUB: %s', self._system, int_id(_src_sub), _target, rule['DST_TS'], int_id(_status[rule['DST_TS']]['TX_GROUP']), int_id(_status[rule['DST_TS']]['TX_SRC_SUB']))
|
||||
continue
|
||||
#
|
||||
# END CONTENTION HANDLING
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# BEGIN FRAME FORWARDING
|
||||
#
|
||||
|
@ -293,7 +296,7 @@ class bridgeIPSC(IPSC):
|
|||
_tmp_data = _data
|
||||
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_target]['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG['SYSTEMS'][_target]['LOCAL']['RADIO_ID'])
|
||||
|
||||
# Re-Write the destination Group ID
|
||||
_tmp_data = _tmp_data.replace(_dst_group, rule['DST_GROUP'])
|
||||
|
@ -322,7 +325,7 @@ class bridgeIPSC(IPSC):
|
|||
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
||||
|
||||
# Send the packet to all peers in the target IPSC
|
||||
networks[_target].send_to_ipsc(_tmp_data)
|
||||
systems[_target].send_to_ipsc(_tmp_data)
|
||||
#
|
||||
# END FRAME FORWARDING
|
||||
#
|
||||
|
@ -350,105 +353,158 @@ class bridgeIPSC(IPSC):
|
|||
if self.last_seq_id != _seq_id:
|
||||
self.last_seq_id = _seq_id
|
||||
self.call_start = time()
|
||||
logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
|
||||
self._logger.info('(%s) GROUP VOICE START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group))
|
||||
|
||||
# Action happens on un-key
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||
if self.last_seq_id == _seq_id:
|
||||
self.call_duration = time() - self.call_start
|
||||
logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
|
||||
self._logger.info('(%s) GROUP VOICE END: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s Duration: %.2fs', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group), self.call_duration)
|
||||
else:
|
||||
logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', _network, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
|
||||
self._logger.warning('(%s) GROUP VOICE END WITHOUT MATCHING START: CallID: %s PEER: %s, SUB: %s, TS: %s, TGID: %s', self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group),)
|
||||
|
||||
# Iterate the rules dictionary
|
||||
for rule in RULES[_network]['GROUP_VOICE']:
|
||||
for rule in RULES[self._system]['GROUP_VOICE']:
|
||||
_target = rule['DST_NET']
|
||||
|
||||
# TGID matches a rule source, reset its timer
|
||||
if _ts == rule['SRC_TS'] and _dst_group == rule['SRC_GROUP'] and ((rule['TO_TYPE'] == 'ON' and (rule['ACTIVE'] == True)) or (rule['TO_TYPE'] == 'OFF' and rule['ACTIVE'] == False)):
|
||||
rule['TIMER'] = now + rule['TIMEOUT']
|
||||
logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', _network, rule['NAME'], rule['TIMER'])
|
||||
self._logger.info('(%s) Source group transmission match for rule \"%s\". Reset timeout to %s', self._system, rule['NAME'], rule['TIMER'])
|
||||
|
||||
# Scan for reciprocal rules and reset their timers as well.
|
||||
for target_rule in RULES[_target]['GROUP_VOICE']:
|
||||
if target_rule['NAME'] == rule['NAME']:
|
||||
target_rule['TIMER'] = now + target_rule['TIMEOUT']
|
||||
logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', _network, target_rule['NAME'], _target, rule['TIMER'])
|
||||
self._logger.info('(%s) Reciprocal group transmission match for rule \"%s\" on IPSC \"%s\". Reset timeout to %s', self._system, target_rule['NAME'], _target, rule['TIMER'])
|
||||
|
||||
# TGID matches an ACTIVATION trigger
|
||||
if _dst_group in rule['ON']:
|
||||
# Set the matching rule as ACTIVE
|
||||
rule['ACTIVE'] = True
|
||||
rule['TIMER'] = now + rule['TIMEOUT']
|
||||
logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE'])
|
||||
self._logger.info('(%s) Primary Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
|
||||
|
||||
# Set reciprocal rules for other IPSCs as ACTIVE
|
||||
for target_rule in RULES[_target]['GROUP_VOICE']:
|
||||
if target_rule['NAME'] == rule['NAME']:
|
||||
target_rule['ACTIVE'] = True
|
||||
target_rule['TIMER'] = now + target_rule['TIMEOUT']
|
||||
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE'])
|
||||
self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
|
||||
|
||||
# TGID matches an DE-ACTIVATION trigger
|
||||
if _dst_group in rule['OFF']:
|
||||
# Set the matching rule as ACTIVE
|
||||
rule['ACTIVE'] = False
|
||||
logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', _network, rule['NAME'], rule['ACTIVE'])
|
||||
self._logger.info('(%s) Bridge Rule \"%s\" changed to state: %s', self._system, rule['NAME'], rule['ACTIVE'])
|
||||
|
||||
# Set reciprocal rules for other IPSCs as ACTIVE
|
||||
_target = rule['DST_NET']
|
||||
for target_rule in RULES[_target]['GROUP_VOICE']:
|
||||
if target_rule['NAME'] == rule['NAME']:
|
||||
target_rule['ACTIVE'] = False
|
||||
logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', _network, target_rule['NAME'], _target, rule['ACTIVE'])
|
||||
self._logger.info('(%s) Reciprocal Bridge Rule \"%s\" in IPSC \"%s\" changed to state: %s', self._system, target_rule['NAME'], _target, rule['ACTIVE'])
|
||||
#
|
||||
# END IN-BAND SIGNALLING
|
||||
#
|
||||
|
||||
|
||||
def group_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||
def group_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
self._logger.debug('(%s) Group Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||
|
||||
for target in RULES[_network]['GROUP_DATA']:
|
||||
for target in RULES[self._system]['GROUP_DATA']:
|
||||
|
||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
||||
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||
_tmp_data = _data
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG[target]['LOCAL']['RADIO_ID'])
|
||||
|
||||
# Send the packet to all peers in the target IPSC
|
||||
networks[target].send_to_ipsc(_tmp_data)
|
||||
systems[target].send_to_ipsc(_tmp_data)
|
||||
|
||||
def private_data(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', _network, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||
def private_data(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
self._logger.debug('(%s) Private Data Packet Received From: %s, IPSC Peer %s, Destination %s', self._system, int_id(_src_sub), int_id(_peerid), int_id(_dst_sub))
|
||||
|
||||
for target in RULES[_network]['PRIVATE_DATA']:
|
||||
for target in RULES[self._system]['PRIVATE_DATA']:
|
||||
|
||||
if self.BRIDGE == True or networks[target].BRIDGE == True:
|
||||
if self.BRIDGE == True or systems[target].BRIDGE == True:
|
||||
_tmp_data = _data
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[target]['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = _tmp_data.replace(_peerid, self._CONFIG[target]['LOCAL']['RADIO_ID'])
|
||||
|
||||
# Send the packet to all peers in the target IPSC
|
||||
networks[target].send_to_ipsc(_tmp_data)
|
||||
systems[target].send_to_ipsc(_tmp_data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import os
|
||||
import signal
|
||||
from dmr_utils.utils import try_download, mk_id_dict
|
||||
|
||||
import dmrlink_log
|
||||
import dmrlink_config
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
# Call the external routine to build the configuration dictionary
|
||||
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||
|
||||
config_reports(CONFIG)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info('DMRlink \'bridge.py\' (c) 2013-2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = bridgeIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
# Shut ourselves down gracefully with the IPSC peers.
|
||||
def sig_handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
|
||||
for system in systems:
|
||||
this_ipsc = systems[system]
|
||||
logger.info('De-Registering from IPSC %s', system)
|
||||
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||
reactor.stop()
|
||||
|
||||
# Set signal handers so that we can gracefully exit if need be
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, sig_handler)
|
||||
|
||||
# Build the routing rules file
|
||||
RULES = build_rules('bridge_rules')
|
||||
|
||||
# Build list of known bridge IDs
|
||||
BRIDGES = build_bridges('known_bridges')
|
||||
|
||||
# Build the Access Control List
|
||||
ACL = build_acl('sub_acl')
|
||||
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||
systems[system] = bridgeIPSC(system, CONFIG, logger, BRIDGES)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||
|
||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||
if REPORTS['REPORT_NETWORKS']:
|
||||
reporting = task.LoopingCall(reporting_loop)
|
||||
reporting.start(REPORTS['REPORT_INTERVAL'])
|
||||
if CONFIG['REPORTS']['REPORT_NETWORKS']:
|
||||
reporting_loop = config_reports(CONFIG)
|
||||
reporting = task.LoopingCall(reporting_loop, logger)
|
||||
reporting.start(CONFIG['REPORTS']['REPORT_INTERVAL'])
|
||||
|
||||
# INITIALIZE THE REPORTING LOOP IF CONFIGURED
|
||||
rule_timer = task.LoopingCall(rule_timer_loop)
|
||||
rule_timer.start(60)
|
||||
|
||||
|
||||
reactor.run()
|
999
dmrlink.py
999
dmrlink.py
File diff suppressed because it is too large
Load Diff
|
@ -64,6 +64,23 @@ LOG_LEVEL: INFO
|
|||
LOG_NAME: DMRlink
|
||||
|
||||
|
||||
# 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.
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
###############################################################################
|
||||
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
###############################################################################
|
||||
|
||||
import ConfigParser
|
||||
import sys
|
||||
|
||||
from socket import gethostbyname
|
||||
|
||||
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__license__ = 'GNU GPLv3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
|
||||
def build_config(_config_file):
|
||||
config = ConfigParser.ConfigParser()
|
||||
|
||||
if not config.read(_config_file):
|
||||
sys.exit('Configuration file \''+_config_file+'\' is not a valid configuration file! Exiting...')
|
||||
|
||||
CONFIG = {}
|
||||
CONFIG['GLOBAL'] = {}
|
||||
CONFIG['REPORTS'] = {}
|
||||
CONFIG['LOGGER'] = {}
|
||||
CONFIG['ALIASES'] = {}
|
||||
CONFIG['SYSTEMS'] = {}
|
||||
|
||||
try:
|
||||
for section in config.sections():
|
||||
if section == 'GLOBAL':
|
||||
CONFIG['GLOBAL'].update({
|
||||
'PATH': config.get(section, 'PATH')
|
||||
})
|
||||
|
||||
elif section == 'REPORTS':
|
||||
CONFIG['REPORTS'].update({
|
||||
'REPORT_NETWORKS': config.get(section, 'REPORT_NETWORKS'),
|
||||
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
|
||||
'REPORT_PATH': config.get(section, 'REPORT_PATH'),
|
||||
'PRINT_PEERS_INC_MODE': config.getboolean(section, 'PRINT_PEERS_INC_MODE'),
|
||||
'PRINT_PEERS_INC_FLAGS': config.getboolean(section, 'PRINT_PEERS_INC_FLAGS')
|
||||
})
|
||||
|
||||
elif section == 'LOGGER':
|
||||
CONFIG['LOGGER'].update({
|
||||
'LOG_FILE': config.get(section, 'LOG_FILE'),
|
||||
'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'),
|
||||
'LOG_LEVEL': config.get(section, 'LOG_LEVEL'),
|
||||
'LOG_NAME': config.get(section, 'LOG_NAME')
|
||||
})
|
||||
|
||||
elif section == 'ALIASES':
|
||||
CONFIG['ALIASES'].update({
|
||||
'TRY_DOWNLOAD': config.getboolean(section, 'TRY_DOWNLOAD'),
|
||||
'PATH': config.get(section, 'PATH'),
|
||||
'PEER_FILE': config.get(section, 'PEER_FILE'),
|
||||
'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'),
|
||||
'TGID_FILE': config.get(section, 'TGID_FILE'),
|
||||
'PEER_URL': config.get(section, 'PEER_URL'),
|
||||
'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'),
|
||||
'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400,
|
||||
})
|
||||
|
||||
elif config.getboolean(section, 'ENABLED'):
|
||||
CONFIG['SYSTEMS'].update({section: {'LOCAL': {}, 'MASTER': {}, 'PEERS': {}}})
|
||||
|
||||
CONFIG['SYSTEMS'][section]['LOCAL'].update({
|
||||
# In case we want to keep config, but not actually connect to the network
|
||||
'ENABLED': config.getboolean(section, 'ENABLED'),
|
||||
|
||||
# These items are used to create the MODE byte
|
||||
'PEER_OPER': config.getboolean(section, 'PEER_OPER'),
|
||||
'IPSC_MODE': config.get(section, 'IPSC_MODE'),
|
||||
'TS1_LINK': config.getboolean(section, 'TS1_LINK'),
|
||||
'TS2_LINK': config.getboolean(section, 'TS2_LINK'),
|
||||
'MODE': '',
|
||||
|
||||
# These items are used to create the multi-byte FLAGS field
|
||||
'AUTH_ENABLED': config.getboolean(section, 'AUTH_ENABLED'),
|
||||
'CSBK_CALL': config.getboolean(section, 'CSBK_CALL'),
|
||||
'RCM': config.getboolean(section, 'RCM'),
|
||||
'CON_APP': config.getboolean(section, 'CON_APP'),
|
||||
'XNL_CALL': config.getboolean(section, 'XNL_CALL'),
|
||||
'XNL_MASTER': config.getboolean(section, 'XNL_MASTER'),
|
||||
'DATA_CALL': config.getboolean(section, 'DATA_CALL'),
|
||||
'VOICE_CALL': config.getboolean(section, 'VOICE_CALL'),
|
||||
'MASTER_PEER': config.getboolean(section, 'MASTER_PEER'),
|
||||
'FLAGS': '',
|
||||
|
||||
# Things we need to know to connect and be a peer in this IPSC
|
||||
'RADIO_ID': hex(int(config.get(section, 'RADIO_ID')))[2:].rjust(8,'0').decode('hex'),
|
||||
'IP': gethostbyname(config.get(section, 'IP')),
|
||||
'PORT': config.getint(section, 'PORT'),
|
||||
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
|
||||
'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
|
||||
'AUTH_KEY': (config.get(section, 'AUTH_KEY').rjust(40,'0')).decode('hex'),
|
||||
'NUM_PEERS': 0,
|
||||
})
|
||||
# Master means things we need to know about the master peer of the network
|
||||
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||
'RADIO_ID': '\x00\x00\x00\x00',
|
||||
'MODE': '\x00',
|
||||
'MODE_DECODE': '',
|
||||
'FLAGS': '\x00\x00\x00\x00',
|
||||
'FLAGS_DECODE': '',
|
||||
'STATUS': {
|
||||
'CONNECTED': False,
|
||||
'PEER_LIST': False,
|
||||
'KEEP_ALIVES_SENT': 0,
|
||||
'KEEP_ALIVES_MISSED': 0,
|
||||
'KEEP_ALIVES_OUTSTANDING': 0,
|
||||
'KEEP_ALIVES_RECEIVED': 0,
|
||||
'KEEP_ALIVE_RX_TIME': 0
|
||||
},
|
||||
'IP': '',
|
||||
'PORT': ''
|
||||
})
|
||||
if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||
'IP': gethostbyname(config.get(section, 'MASTER_IP')),
|
||||
'PORT': config.getint(section, 'MASTER_PORT')
|
||||
})
|
||||
|
||||
# Temporary locations for building MODE and FLAG data
|
||||
MODE_BYTE = 0
|
||||
FLAG_1 = 0
|
||||
FLAG_2 = 0
|
||||
|
||||
# Construct and store the MODE field
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['PEER_OPER']:
|
||||
MODE_BYTE |= 1 << 6
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'ANALOG':
|
||||
MODE_BYTE |= 1 << 4
|
||||
elif CONFIG['SYSTEMS'][section]['LOCAL']['IPSC_MODE'] == 'DIGITAL':
|
||||
MODE_BYTE |= 1 << 5
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['TS1_LINK']:
|
||||
MODE_BYTE |= 1 << 3
|
||||
else:
|
||||
MODE_BYTE |= 1 << 2
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['TS2_LINK']:
|
||||
MODE_BYTE |= 1 << 1
|
||||
else:
|
||||
MODE_BYTE |= 1 << 0
|
||||
CONFIG['SYSTEMS'][section]['LOCAL']['MODE'] = chr(MODE_BYTE)
|
||||
|
||||
# Construct and store the FLAGS field
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['CSBK_CALL']:
|
||||
FLAG_1 |= 1 << 7
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['RCM']:
|
||||
FLAG_1 |= 1 << 6
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['CON_APP']:
|
||||
FLAG_1 |= 1 << 5
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL']:
|
||||
FLAG_2 |= 1 << 7
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']:
|
||||
FLAG_2 |= 1 << 6
|
||||
elif CONFIG['SYSTEMS'][section]['LOCAL']['XNL_CALL'] and not CONFIG['SYSTEMS'][section]['LOCAL']['XNL_MASTER']:
|
||||
FLAG_2 |= 1 << 5
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['AUTH_ENABLED']:
|
||||
FLAG_2 |= 1 << 4
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['DATA_CALL']:
|
||||
FLAG_2 |= 1 << 3
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['VOICE_CALL']:
|
||||
FLAG_2 |= 1 << 2
|
||||
if CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||
FLAG_2 |= 1 << 0
|
||||
CONFIG['SYSTEMS'][section]['LOCAL']['FLAGS'] = '\x00\x00'+chr(FLAG_1)+chr(FLAG_2)
|
||||
|
||||
except ConfigParser.Error, err:
|
||||
sys.exit('Could not parse configuration file, exiting...')
|
||||
|
||||
return CONFIG
|
||||
|
||||
|
||||
# Used to run this file direclty and print the config,
|
||||
# which might be useful for debugging
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
from pprint import pprint
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
|
||||
# Ensure we have a path for the config file, if one wasn't specified, then use the execution directory
|
||||
if not cli_args.CONFIG_FILE:
|
||||
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
|
||||
pprint(build_config(cli_args.CONFIG_FILE))
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
###############################################################################
|
||||
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
|
||||
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__license__ = 'GNU GPLv3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
|
||||
def config_logging(_logger):
|
||||
dictConfig({
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
},
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
|
||||
},
|
||||
'timed': {
|
||||
'format': '%(levelname)s %(asctime)s %(message)s'
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
},
|
||||
'syslog': {
|
||||
'format': '%(name)s (%(process)d): %(levelname)s %(message)s'
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'null': {
|
||||
'class': 'logging.NullHandler'
|
||||
},
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple'
|
||||
},
|
||||
'console-timed': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'timed'
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'simple',
|
||||
'filename': _logger['LOG_FILE'],
|
||||
},
|
||||
'file-timed': {
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'timed',
|
||||
'filename': _logger['LOG_FILE'],
|
||||
},
|
||||
'syslog': {
|
||||
'class': 'logging.handlers.SysLogHandler',
|
||||
'formatter': 'syslog',
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
_logger['LOG_NAME']: {
|
||||
'handlers': _logger['LOG_HANDLERS'].split(','),
|
||||
'level': _logger['LOG_LEVEL'],
|
||||
'propagate': True,
|
||||
}
|
||||
}
|
||||
})
|
||||
return logging.getLogger(_logger['LOG_NAME'])
|
|
@ -45,6 +45,14 @@ IPSC_VER_22 = '\x04'
|
|||
# Link Type Values - assumed that cap+, etc. are different, this is all I can confirm
|
||||
LINK_TYPE_IPSC = '\x04'
|
||||
|
||||
# Burst Data Types
|
||||
BURST_DATA_TYPE = {
|
||||
'VOICE_HEAD': '\x01',
|
||||
'VOICE_TERM': '\x02',
|
||||
'SLOT1_VOICE': '\x0A',
|
||||
'SLOT2_VOICE': '\x8A'
|
||||
}
|
||||
|
||||
# IPSC Version and Link Type are Used for a 4-byte version field in registration packets
|
||||
IPSC_VER = LINK_TYPE_IPSC + IPSC_VER_17 + LINK_TYPE_IPSC + IPSC_VER_16
|
||||
|
129
log.py
129
log.py
|
@ -25,7 +25,8 @@ from twisted.internet import reactor
|
|||
from binascii import b2a_hex as h
|
||||
|
||||
import time
|
||||
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
||||
from dmrlink import IPSC, systems
|
||||
from dmr_utils.utils import hex_str_3, hex_str_4, int_id, get_alias
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
|
@ -36,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()
|
4719
peer_ids.csv
4719
peer_ids.csv
File diff suppressed because it is too large
Load Diff
|
@ -32,11 +32,14 @@
|
|||
|
||||
from __future__ import print_function
|
||||
from twisted.internet import reactor
|
||||
from binascii import b2a_hex as h
|
||||
|
||||
import sys, time
|
||||
import cPickle as pickle
|
||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3
|
||||
|
||||
from dmrlink import IPSC, systems
|
||||
|
||||
from dmr_utils.utils import int_id, hex_str_3
|
||||
from ipsc.ipsc_const import BURST_DATA_TYPE
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2014 - 2015 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
|
@ -45,14 +48,6 @@ __license__ = 'GNU GPLv3'
|
|||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
# Constants for this application
|
||||
#
|
||||
BURST_DATA_TYPE = {
|
||||
'VOICE_HEAD': '\x01',
|
||||
'VOICE_TERM': '\x02',
|
||||
'SLOT1_VOICE': '\x0A',
|
||||
'SLOT2_VOICE': '\x8A'
|
||||
}
|
||||
|
||||
# path+filename for the transmission to play back
|
||||
filename = '../test.pickle'
|
||||
|
@ -66,9 +61,8 @@ trigger_groups_1 = ['\x00\x00\x01', '\x00\x00\x0D', '\x00\x00\x64']
|
|||
trigger_groups_2 = ['\x00\x0C\x30',]
|
||||
|
||||
class playIPSC(IPSC):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
def __init__(self, _name, _config, _logger):
|
||||
IPSC.__init__(self, _name, _config, _logger)
|
||||
self.CALL_DATA = []
|
||||
self.event_id = 1
|
||||
|
||||
|
@ -76,30 +70,30 @@ class playIPSC(IPSC):
|
|||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
#
|
||||
def group_voice(self, _network, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||
def group_voice(self, _src_sub, _dst_group, _ts, _end, _peerid, _data):
|
||||
if _end:
|
||||
_self_peer = NETWORK[_network]['LOCAL']['RADIO_ID']
|
||||
_self_peer = self._config['LOCAL']['RADIO_ID']
|
||||
_self_src = _self_peer[1:]
|
||||
|
||||
if (_peerid == _self_peer) or (_src_sub == _self_src):
|
||||
logger.error('(%s) Just received a packet that appears to have been originated by us. PeerID: %s Subscriber: %s TS: %s, TGID: %s', _network, int_id(_peerid), int_id(_src_sub), int(_ts)+1, int_id(_dst_group))
|
||||
self._logger.error('(%s) Just received a packet that appears to have been originated by us. PeerID: %s Subscriber: %s TS: %s, TGID: %s', self._system, int_id(_peerid), int_id(_src_sub), int(_ts), int_id(_dst_group))
|
||||
return
|
||||
|
||||
if trigger == False:
|
||||
if (_ts == 0 and _dst_group not in trigger_groups_1) or (_ts == 1 and _dst_group not in trigger_groups_2):
|
||||
if (_ts == 1 and _dst_group not in trigger_groups_1) or (_ts == 2 and _dst_group not in trigger_groups_2):
|
||||
return
|
||||
else:
|
||||
if (_ts == 0 and _dst_group in trigger_groups_1) or (_ts == 1 and _dst_group in trigger_groups_2):
|
||||
if (_ts == 1 and _dst_group not in trigger_groups_1) or (_ts == 2 and _dst_group not in trigger_groups_2):
|
||||
return
|
||||
|
||||
logger.info('(%s) Event ID: %s - Playback triggered from SourceID: %s, TS: %s, TGID: %s, PeerID: %s', _network, self.event_id, int_id(_src_sub), _ts+1, int_id(_dst_group), int_id(_peerid))
|
||||
self._logger.info('(%s) Event ID: %s - Playback triggered from SourceID: %s, TS: %s, TGID: %s, PeerID: %s', self._system, self.event_id, int_id(_src_sub), _ts, int_id(_dst_group), int_id(_peerid))
|
||||
|
||||
# Determine the type of voice packet this is (see top of file for possible types)
|
||||
_burst_data_type = _data[30]
|
||||
|
||||
time.sleep(2)
|
||||
self.CALL_DATA = pickle.load(open(filename, 'rb'))
|
||||
logger.info('(%s) Event ID: %s - Playing back file: %s', _network, self.event_id, filename)
|
||||
self._logger.info('(%s) Event ID: %s - Playing back file: %s', self._system, self.event_id, filename)
|
||||
|
||||
for i in self.CALL_DATA:
|
||||
_tmp_data = i
|
||||
|
@ -113,9 +107,9 @@ class playIPSC(IPSC):
|
|||
|
||||
# Re-Write IPSC timeslot value
|
||||
_call_info = int_id(_tmp_data[17:18])
|
||||
if _ts == 0:
|
||||
if _ts == 1:
|
||||
_call_info &= ~(1 << 5)
|
||||
elif _ts == 1:
|
||||
elif _ts == 2:
|
||||
_call_info |= 1 << 5
|
||||
_call_info = chr(_call_info)
|
||||
_tmp_data = _tmp_data[:17] + _call_info + _tmp_data[18:]
|
||||
|
@ -124,9 +118,9 @@ class playIPSC(IPSC):
|
|||
# Determine if the slot is present, so we can translate if need be
|
||||
if _burst_data_type == BURST_DATA_TYPE['SLOT1_VOICE'] or _burst_data_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
||||
# Re-Write timeslot if necessary...
|
||||
if _ts == 0:
|
||||
if _ts == 1:
|
||||
_burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE']
|
||||
elif _ts == 1:
|
||||
elif _ts == 2:
|
||||
_burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE']
|
||||
_tmp_data = _tmp_data[:30] + _burst_data_type + _tmp_data[31:]
|
||||
|
||||
|
@ -134,13 +128,58 @@ class playIPSC(IPSC):
|
|||
self.send_to_ipsc(_tmp_data)
|
||||
time.sleep(0.06)
|
||||
self.CALL_DATA = []
|
||||
logger.info('(%s) Event ID: %s - Playback Completed', _network, self.event_id)
|
||||
self._logger.info('(%s) Event ID: %s - Playback Completed', self._system, self.event_id)
|
||||
self.event_id = self.event_id + 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
|
||||
import dmrlink_log
|
||||
import dmrlink_config
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
# Call the external routine to build the configuration dictionary
|
||||
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||
|
||||
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = playIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
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()
|
98
playback.py
98
playback.py
|
@ -22,10 +22,11 @@
|
|||
|
||||
from __future__ import print_function
|
||||
from twisted.internet import reactor
|
||||
from binascii import b2a_hex as h
|
||||
from binascii import b2a_hex as ahex
|
||||
|
||||
import sys, time
|
||||
from dmrlink import IPSC, NETWORK, networks, logger, dmr_nat, int_id, hex_str_3
|
||||
from dmrlink import IPSC, systems
|
||||
from dmr_utils.utils import int_id, hex_str_3
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
|
@ -43,62 +44,66 @@ except ImportError:
|
|||
HEX_TGID = hex_str_3(TGID)
|
||||
HEX_SUB = hex_str_3(SUB)
|
||||
BOGUS_SUB = '\xFF\xFF\xFF'
|
||||
if GROUP_SRC_SUB:
|
||||
logger.info('Playback: USING SUBSCRIBER ID: %s FOR GROUP REPEAT', GROUP_SRC_SUB)
|
||||
HEX_GRP_SUB = hex_str_3(GROUP_SRC_SUB)
|
||||
|
||||
class playbackIPSC(IPSC):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
def __init__(self, _name, _config, _logger):
|
||||
IPSC.__init__(self, _name, _config, _logger)
|
||||
self.CALL_DATA = []
|
||||
|
||||
if GROUP_SRC_SUB:
|
||||
self._logger.info('Playback: USING SUBSCRIBER ID: %s FOR GROUP REPEAT', GROUP_SRC_SUB)
|
||||
self.GROUP_SRC_SUB = hex_str_3(GROUP_SRC_SUB)
|
||||
|
||||
if GROUP_REPEAT:
|
||||
self._logger.info('Playback: GROUP REPEAT ENABLED')
|
||||
|
||||
if PRIVATE_REPEAT:
|
||||
self._logger.info('Playback: PRIVATE REPEAT ENABLED')
|
||||
|
||||
#************************************************
|
||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
#
|
||||
if GROUP_REPEAT:
|
||||
logger.info('Playback: DEFINING GROUP REPEAT FUNCTION')
|
||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
if HEX_TGID == _dst_sub and _ts in GROUP_TS:
|
||||
if not _end:
|
||||
if not self.CALL_DATA:
|
||||
logger.info('(%s) Receiving transmission to be played back from subscriber: %s', _network, int_id(_src_sub))
|
||||
self._logger.info('(%s) Receiving transmission to be played back from subscriber: %s', self._system, int_id(_src_sub))
|
||||
_tmp_data = _data
|
||||
#_tmp_data = dmr_nat(_data, _src_sub, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
||||
#_tmp_data = dmr_nat(_data, _src_sub, self._config['LOCAL']['RADIO_ID'])
|
||||
self.CALL_DATA.append(_tmp_data)
|
||||
if _end:
|
||||
self.CALL_DATA.append(_data)
|
||||
time.sleep(2)
|
||||
logger.info('(%s) Playing back transmission from subscriber: %s', _network, int_id(_src_sub))
|
||||
self._logger.info('(%s) Playing back transmission from subscriber: %s', self._system, int_id(_src_sub))
|
||||
for i in self.CALL_DATA:
|
||||
_tmp_data = i
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = _tmp_data.replace(_peerid, self._config['LOCAL']['RADIO_ID'])
|
||||
if GROUP_SRC_SUB:
|
||||
_tmp_data = _tmp_data.replace(_src_sub, HEX_GRP_SUB)
|
||||
_tmp_data = _tmp_data.replace(_src_sub, self.GROUP_SRC_SUB)
|
||||
# Send the packet to all peers in the target IPSC
|
||||
self.send_to_ipsc(_tmp_data)
|
||||
time.sleep(0.06)
|
||||
self.CALL_DATA = []
|
||||
|
||||
if PRIVATE_REPEAT:
|
||||
logger.info('Playback: DEFINING PRIVATE REPEAT FUNCTION')
|
||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
if HEX_SUB == _dst_sub and _ts in PRIVATE_TS:
|
||||
if not _end:
|
||||
if not self.CALL_DATA:
|
||||
logger.info('(%s) Receiving transmission to be played back from subscriber: %s, to subscriber: %s', _network, int_id(_src_sub), int_id(_dst_sub))
|
||||
self._logger.info('(%s) Receiving transmission to be played back from subscriber: %s, to subscriber: %s', self._system, int_id(_src_sub), int_id(_dst_sub))
|
||||
_tmp_data = _data
|
||||
self.CALL_DATA.append(_tmp_data)
|
||||
if _end:
|
||||
self.CALL_DATA.append(_data)
|
||||
time.sleep(1)
|
||||
logger.info('(%s) Playing back transmission from subscriber: %s, to subscriber %s', _network, int_id(_src_sub), int_id(_dst_sub))
|
||||
self._logger.info('(%s) Playing back transmission from subscriber: %s, to subscriber %s', self._system, int_id(_src_sub), int_id(_dst_sub))
|
||||
_orig_src = _src_sub
|
||||
_orig_dst = _dst_sub
|
||||
for i in self.CALL_DATA:
|
||||
_tmp_data = i
|
||||
_tmp_data = _tmp_data.replace(_peerid, NETWORK[_network]['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = _tmp_data.replace(_peerid, self._config['LOCAL']['RADIO_ID'])
|
||||
_tmp_data = _tmp_data.replace(_dst_sub, BOGUS_SUB)
|
||||
_tmp_data = _tmp_data.replace(_src_sub, _orig_dst)
|
||||
_tmp_data = _tmp_data.replace(BOGUS_SUB, _orig_src)
|
||||
|
@ -107,10 +112,55 @@ class playbackIPSC(IPSC):
|
|||
time.sleep(0.06)
|
||||
self.CALL_DATA = []
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
|
||||
import dmrlink_log
|
||||
import dmrlink_config
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
# Call the external routine to build the configuration dictionary
|
||||
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||
|
||||
logger.info('DMRlink \'playback.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = playbackIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
|
||||
# Shut ourselves down gracefully with the IPSC peers.
|
||||
def sig_handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
|
||||
for system in systems:
|
||||
this_ipsc = systems[system]
|
||||
logger.info('De-Registering from IPSC %s', system)
|
||||
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||
reactor.stop()
|
||||
|
||||
# Set signal handers so that we can gracefully exit if need be
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, sig_handler)
|
||||
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||
systems[system] = playbackIPSC(system, CONFIG, logger)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||
|
||||
reactor.run()
|
||||
|
|
115
rcm.py
115
rcm.py
|
@ -26,12 +26,13 @@ from __future__ import print_function
|
|||
from twisted.internet.protocol import DatagramProtocol
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet import task
|
||||
from binascii import b2a_hex as h
|
||||
from binascii import b2a_hex as ahex
|
||||
|
||||
import datetime
|
||||
import binascii
|
||||
import dmrlink
|
||||
from dmrlink import IPSC, NETWORK, networks, get_info, int_id, subscriber_ids, peer_ids, talkgroup_ids, logger
|
||||
from dmrlink import IPSC, systems
|
||||
from dmr_utils.utils import get_alias, int_id
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013, 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
|
@ -51,15 +52,14 @@ rpt = True
|
|||
nack = True
|
||||
|
||||
class rcmIPSC(IPSC):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
def __init__(self, _name, _config, _logger):
|
||||
IPSC.__init__(self, _name, _config, _logger)
|
||||
|
||||
#************************************************
|
||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
#
|
||||
def call_mon_status(self, _network, _data):
|
||||
def call_mon_status(self, _data):
|
||||
if not status:
|
||||
return
|
||||
_source = _data[1:5]
|
||||
|
@ -73,19 +73,19 @@ class rcmIPSC(IPSC):
|
|||
_prio = _data[23]
|
||||
_sec = _data[24]
|
||||
|
||||
_source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids))
|
||||
_ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_info(int_id(_ipsc_src), peer_ids))
|
||||
_rf_src = str(int_id(_rf_src)) + ', ' + str(get_info(int_id(_rf_src), subscriber_ids))
|
||||
_source = str(int_id(_source)) + ', ' + str(get_alias(_source, peer_ids))
|
||||
_ipsc_src = str(int_id(_ipsc_src)) + ', ' + str(get_alias(_ipsc_src, peer_ids))
|
||||
_rf_src = str(int_id(_rf_src)) + ', ' + str(get_alias(_rf_src, subscriber_ids))
|
||||
|
||||
if _type == '\x4F' or '\x51':
|
||||
_rf_tgt = 'TGID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_info(int_id(_rf_tgt), talkgroup_ids))
|
||||
_rf_tgt = 'TGID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_alias(_rf_tgt, talkgroup_ids))
|
||||
else:
|
||||
_rf_tgt = 'SID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_info(int_id(_rf_tgt), subscriber_ids))
|
||||
_rf_tgt = 'SID: ' + str(int_id(_rf_tgt)) + ', ' + str(get_alias(_rf_tgt, subscriber_ids))
|
||||
|
||||
print('Call Monitor - Call Status')
|
||||
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
print('DATA SOURCE: ', _source)
|
||||
print('IPSC: ', _network)
|
||||
print('IPSC: ', self._system)
|
||||
print('IPSC Source: ', _ipsc_src)
|
||||
print('Timeslot: ', TS[_ts])
|
||||
try:
|
||||
|
@ -100,14 +100,14 @@ class rcmIPSC(IPSC):
|
|||
print('Target Sub: ', _rf_tgt)
|
||||
print()
|
||||
|
||||
def call_mon_rpt(self, _network, _data):
|
||||
def call_mon_rpt(self, _data):
|
||||
if not rpt:
|
||||
return
|
||||
_source = _data[1:5]
|
||||
_ts1_state = _data[5]
|
||||
_ts2_state = _data[6]
|
||||
|
||||
_source = str(int_id(_source)) + ', ' + str(get_info(int_id(_source), peer_ids))
|
||||
_source = str(int_id(_source)) + ', ' + str(get_alias(_source, peer_ids))
|
||||
|
||||
print('Call Monitor - Repeater State')
|
||||
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
@ -123,13 +123,13 @@ class rcmIPSC(IPSC):
|
|||
print('TS2 State (unknown): ', h(_ts2_state))
|
||||
print()
|
||||
|
||||
def call_mon_nack(self, _network, _data):
|
||||
def call_mon_nack(self, _data):
|
||||
if not nack:
|
||||
return
|
||||
_source = _data[1:5]
|
||||
_nack = _data[5]
|
||||
|
||||
_source = get_info(int_id(_source), peer_ids)
|
||||
_source = get_alias(_source, peer_ids)
|
||||
|
||||
print('Call Monitor - Transmission NACK')
|
||||
print('TIME: ', datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
@ -140,17 +140,84 @@ class rcmIPSC(IPSC):
|
|||
print('NACK Cause (unknown): ', h(_nack))
|
||||
print()
|
||||
|
||||
def repeater_wake_up(self, _network, _data):
|
||||
def repeater_wake_up(self, _data):
|
||||
_source = _data[1:5]
|
||||
_source_dec = int_id(_source)
|
||||
_source_name = get_info(_source_dec, peer_ids)
|
||||
#print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(_network, _source_name, _source_dec))
|
||||
_source_name = get_alias(_source, peer_ids)
|
||||
print('({}) Repeater Wake-Up Packet Received: {} ({})' .format(self._system, _source_name, int_id(_source)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
from dmr_utils.utils import try_download, mk_id_dict
|
||||
|
||||
import dmrlink_log
|
||||
import dmrlink_config
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
# Call the external routine to build the configuration dictionary
|
||||
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||
|
||||
logger.info('DMRlink \'rcm.py\' (c) 2013, 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = rcmIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
|
||||
# ID ALIAS CREATION
|
||||
# Download
|
||||
if CONFIG['ALIASES']['TRY_DOWNLOAD'] == True:
|
||||
# Try updating peer aliases file
|
||||
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'], CONFIG['ALIASES']['PEER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||
logger.info(result)
|
||||
# Try updating subscriber aliases file
|
||||
result = try_download(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'], CONFIG['ALIASES']['SUBSCRIBER_URL'], CONFIG['ALIASES']['STALE_TIME'])
|
||||
logger.info(result)
|
||||
|
||||
# Make Dictionaries
|
||||
peer_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['PEER_FILE'])
|
||||
if peer_ids:
|
||||
logger.info('ID ALIAS MAPPER: peer_ids dictionary is available')
|
||||
|
||||
subscriber_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['SUBSCRIBER_FILE'])
|
||||
if subscriber_ids:
|
||||
logger.info('ID ALIAS MAPPER: subscriber_ids dictionary is available')
|
||||
|
||||
talkgroup_ids = mk_id_dict(CONFIG['ALIASES']['PATH'], CONFIG['ALIASES']['TGID_FILE'])
|
||||
if talkgroup_ids:
|
||||
logger.info('ID ALIAS MAPPER: talkgroup_ids dictionary is available')
|
||||
|
||||
# Shut ourselves down gracefully with the IPSC peers.
|
||||
def sig_handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
|
||||
for system in systems:
|
||||
this_ipsc = systems[system]
|
||||
logger.info('De-Registering from IPSC %s', system)
|
||||
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||
reactor.stop()
|
||||
|
||||
# Set signal handers so that we can gracefully exit if need be
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, sig_handler)
|
||||
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||
systems[system] = rcmIPSC(system, CONFIG, logger)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||
|
||||
reactor.run()
|
80
record.py
80
record.py
|
@ -27,7 +27,8 @@ from binascii import b2a_hex as h
|
|||
|
||||
import sys
|
||||
import cPickle as pickle
|
||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3
|
||||
from dmrlink import IPSC, systems
|
||||
from dmr_utils.utils import hex_str_3, int_id
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2014 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
|
@ -49,11 +50,11 @@ while True:
|
|||
ts = raw_input('Which timeslot (1, 2 or \'both\')? ')
|
||||
if ts == '1' or ts == '2' or ts =='both':
|
||||
if ts == '1':
|
||||
ts = (0,)
|
||||
if ts == '2':
|
||||
ts = (1,)
|
||||
if ts == '2':
|
||||
ts = (2,)
|
||||
if ts == 'both':
|
||||
ts = (0,1)
|
||||
ts = (1,2)
|
||||
break
|
||||
print('...input must be \'1\', \'2\' or \'both\'')
|
||||
|
||||
|
@ -64,9 +65,8 @@ id = hex_str_3(id)
|
|||
filename = raw_input('Filename to use for this recording? ')
|
||||
|
||||
class recordIPSC(IPSC):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
IPSC.__init__(self, *args, **kwargs)
|
||||
def __init__(self, _name, _config, _logger):
|
||||
IPSC.__init__(self, _name, _config, _logger)
|
||||
self.CALL_DATA = []
|
||||
|
||||
#************************************************
|
||||
|
@ -75,39 +75,83 @@ class recordIPSC(IPSC):
|
|||
#
|
||||
if tx_type == 'g':
|
||||
print('Initializing to record GROUP VOICE transmission')
|
||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
if id == _dst_sub and _ts in ts:
|
||||
if not _end:
|
||||
if not self.CALL_DATA:
|
||||
print('({}) Recording transmission from subscriber: {}' .format(_network, int_id(_src_sub)))
|
||||
print('({}) Recording transmission from subscriber: {}' .format(self._system, int_id(_src_sub)))
|
||||
self.CALL_DATA.append(_data)
|
||||
if _end:
|
||||
self.CALL_DATA.append(_data)
|
||||
print('({}) Transmission ended, writing to disk: {}' .format(_network, filename))
|
||||
print('({}) Transmission ended, writing to disk: {}' .format(self._system, filename))
|
||||
pickle.dump(self.CALL_DATA, open(filename, 'wb'))
|
||||
reactor.stop()
|
||||
print('Recording created, program terminating')
|
||||
|
||||
if tx_type == 'p':
|
||||
print('Initializing ro record PRIVATE VOICE transmission')
|
||||
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
if id == _dst_sub and _ts in ts:
|
||||
if not _end:
|
||||
if not self.CALL_DATA:
|
||||
print('({}) Recording transmission from subscriber: {}' .format(_network, int_id(_src_sub)))
|
||||
print('({}) Recording transmission from subscriber: {}' .format(self._system, int_id(_src_sub)))
|
||||
self.CALL_DATA.append(_data)
|
||||
if _end:
|
||||
self.CALL_DATA.append(_data)
|
||||
print('({}) Transmission ended, writing to disk: {}' .format(_network, filename))
|
||||
print('({}) Transmission ended, writing to disk: {}' .format(self._system, filename))
|
||||
pickle.dump(self.CALL_DATA, open(filename, 'wb'))
|
||||
reactor.stop()
|
||||
print('Recording created, program terminating')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
|
||||
import dmrlink_log
|
||||
import dmrlink_config
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
# CLI argument parser - handles picking up the config file from the command line, and sending a "help" message
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-c', '--config', action='store', dest='CFG_FILE', help='/full/path/to/config.file (usually dmrlink.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
# Call the external routine to build the configuration dictionary
|
||||
CONFIG = dmrlink_config.build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
logger = dmrlink_log.config_logging(CONFIG['LOGGER'])
|
||||
|
||||
logger.info('DMRlink \'record.py\' (c) 2014 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
for ipsc_network in NETWORK:
|
||||
if NETWORK[ipsc_network]['LOCAL']['ENABLED']:
|
||||
networks[ipsc_network] = recordIPSC(ipsc_network)
|
||||
reactor.listenUDP(NETWORK[ipsc_network]['LOCAL']['PORT'], networks[ipsc_network], interface=NETWORK[ipsc_network]['LOCAL']['IP'])
|
||||
|
||||
# Shut ourselves down gracefully with the IPSC peers.
|
||||
def sig_handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
|
||||
for system in systems:
|
||||
this_ipsc = systems[system]
|
||||
logger.info('De-Registering from IPSC %s', system)
|
||||
de_reg_req_pkt = this_ipsc.hashed_packet(this_ipsc._local['AUTH_KEY'], this_ipsc.DE_REG_REQ_PKT)
|
||||
this_ipsc.send_to_ipsc(de_reg_req_pkt)
|
||||
reactor.stop()
|
||||
|
||||
# Set signal handers so that we can gracefully exit if need be
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, sig_handler)
|
||||
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGUED IPSC
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['LOCAL']['ENABLED']:
|
||||
systems[system] = recordIPSC(system, CONFIG, logger)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||
|
||||
reactor.run()
|
||||
|
|
75733
subscriber_ids.csv
75733
subscriber_ids.csv
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +1 @@
|
|||
Worldwide,1
|
||||
Local,2
|
||||
North America,3
|
||||
BrandMeister,9
|
||||
Worldwide English,13
|
||||
TAC 310,310
|
||||
DCI Bridge 2,3100
|
||||
DCI 1,3160
|
||||
Midwest,3169
|
||||
Northeast,3172
|
||||
Southeast,3174
|
||||
Flordia,3112
|
||||
Kansas Statewide,3120
|
||||
Massachussetts,3125
|
||||
Missouri,3129
|
||||
DCI Comm 1,3777215
|
||||
Echo Server,9998
|
||||
1,Worldwide
2,Local
3,North America
9,BrandMeister
13,Worldwide English
310,TAC 310
3100,DCI Bridge 2
3160,DCI 1
3169,Midwest
3172,Northeast
3174,Southeast
3112,Flordia
3120,Kansas Statewide
3125,Massachussetts
3129,Missouri
31201,BYRG KC
3777215,DCI Comm 1
9998,Echo Server
|
|
Loading…
Reference in New Issue