Merge ambe_audio_2way into master
This commit is contained in:
parent
f367602237
commit
b47fd15155
@ -1,9 +1,53 @@
|
|||||||
[SETTINGS]
|
################################################
|
||||||
_debug = False
|
# ambe_audio configuration file.
|
||||||
_outToFile = False
|
################################################
|
||||||
_outToUDP = True
|
|
||||||
_gateway = 127.0.0.1
|
# DEFAULTS - General settings. These values are
|
||||||
_gateway_port = 1234
|
#inherited in each subsequent section (defined by section value).
|
||||||
_remote_control_port = 1235
|
[DEFAULTS]
|
||||||
_tg_filter = 2,3,13,3174,3777215,3100,9,9998,3112
|
debug = False # Debug output for each VOICE frame
|
||||||
|
outToFile = False # Write each AMBE frame to a file called ambe.bin
|
||||||
|
outToUDP = True # Send each AMBE frame to the _sock object (turn on/off DMRGateway operation)
|
||||||
|
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
|
||||||
|
txTg = 9 # TG to use for all frames received from DMRGateway -> IPSC
|
||||||
|
txTs = 2 # Slot to use for frames received from DMRGateway -> IPSC
|
||||||
|
#
|
||||||
|
# The section setting defines the current section to use. By default, the ‘ENABLED’ section in dmrlink.cfg is used.
|
||||||
|
# Any values in the named section override the values from the DEFAULTS section. For example, if the BM section
|
||||||
|
# has a value for gatewayDmrId it would override the value above. Only one section should be set here. Think
|
||||||
|
# of this as an easy way to switch between several different configurations with a single line.
|
||||||
|
#
|
||||||
|
#section = BM # Use BM section values
|
||||||
|
#section = Sandbox # Use SANDBOX section values
|
||||||
|
|
||||||
|
[BM] # BrandMeister
|
||||||
|
tgFilter = 3100,31094 # A list of TG IDs to monitor. All TGs will be passed to DMRGateway
|
||||||
|
txTg = 3100 # TG to use for all frames received from DMRGateway -> IPSC
|
||||||
|
txTs = 2 # Slot to use for frames received from DMRGateway -> IPSC
|
||||||
|
|
||||||
|
[BM2] # Alternate BM configuration
|
||||||
|
tgFilter = 31094
|
||||||
|
txTg = 31094
|
||||||
|
txTs = 2
|
||||||
|
|
||||||
|
[Sandbox] # DMR MARC sandbox network
|
||||||
|
tgFilter = 3120
|
||||||
|
txTg = 3120
|
||||||
|
txTs = 2
|
||||||
|
|
||||||
|
[Sandbox2] # DMR MARC sandbox network
|
||||||
|
tgFilter = 1
|
||||||
|
txTg = 1
|
||||||
|
txTs = 1
|
||||||
|
|
||||||
|
[N4IRS] # N4IRS/INAD network
|
||||||
|
tgFilter = 1,2,3,13,3174,3777215,3100,9,9998,3112,3136,310,311,312,9997
|
||||||
|
txTg = 9998
|
||||||
|
txTs = 2
|
||||||
|
|
||||||
|
|
||||||
|
544
ambe_audio.py
544
ambe_audio.py
@ -16,10 +16,11 @@ from bitstring import BitArray
|
|||||||
|
|
||||||
import sys, socket, ConfigParser, thread, traceback
|
import sys, socket, ConfigParser, thread, traceback
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from dmrlink import IPSC, NETWORK, networks, logger, int_id, hex_str_3, get_info, talkgroup_ids, subscriber_ids, peer_ids, PATH
|
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 time import time
|
from time import time, sleep, clock, localtime, strftime
|
||||||
import csv
|
import csv
|
||||||
|
import struct
|
||||||
|
from random import randint
|
||||||
|
|
||||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||||
@ -35,55 +36,84 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
sys.exit('IPSC message types file not found or invalid')
|
sys.exit('IPSC message types file not found or invalid')
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ipsc.ipsc_mask import *
|
||||||
|
except ImportError:
|
||||||
|
sys.exit('IPSC mask values file not found or invalid')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# ambeIPSC class,
|
# ambeIPSC class,
|
||||||
#
|
#
|
||||||
class ambeIPSC(IPSC):
|
class ambeIPSC(IPSC):
|
||||||
|
|
||||||
_configFile='ambe_audio.cfg'
|
_configFile='ambe_audio.cfg' # Name of the config file to over-ride these default values
|
||||||
_debug = False
|
_debug = False # Debug output for each VOICE frame
|
||||||
_outToFile = False
|
_outToFile = False # Write each AMBE frame to a file called ambe.bin
|
||||||
_outToUDP = True
|
_outToUDP = True # Send each AMBE frame to the _sock object (turn on/off DMRGateway operation)
|
||||||
#_gateway = "192.168.1.184"
|
#_gateway = "192.168.1.184"
|
||||||
_gateway = "127.0.0.1"
|
_gateway = "127.0.0.1" # IP address of DMRGateway app
|
||||||
_gateway_port = 1234
|
_gateway_port = 31000 # Port DMRGateway is listening on for AMBE frames to decode
|
||||||
_remote_control_port = 1235
|
_remote_control_port = 31002 # Port that ambe_audio is listening on for remote control commands
|
||||||
|
_ambeRxPort = 31003 # Port to listen on for AMBE frames to transmit to all peers
|
||||||
|
_gateway_dmr_id = 0 # id to use when transmitting from the gateway
|
||||||
_tg_filter = [2,3,13,3174,3777215,3100,9,9998,3112] #set this to the tg to monitor
|
_tg_filter = [2,3,13,3174,3777215,3100,9,9998,3112] #set this to the tg to monitor
|
||||||
_no_tg = -99
|
|
||||||
_sock = -1;
|
_no_tg = -99 # Flag (const) that defines a value for "no tg is currently active"
|
||||||
lastPacketTimeout = 0
|
_busy_slots = [0,0,0] # Keep track of activity on each slot. Make sure app is polite
|
||||||
_transmitStartTime = 0
|
_sock = -1; # Socket object to send AMBE to DMRGateway
|
||||||
|
lastPacketTimeout = 0 # Time of last packet. Used to trigger an artifical TERM if one was not seen
|
||||||
|
_transmitStartTime = 0 # Used for info on transmission duration
|
||||||
|
_start_seq = 0 # Used to maintain error statistics for a transmission
|
||||||
|
_packet_count = 0 # Used to maintain error statistics for a transmission
|
||||||
|
_seq = 0 # Transmit frame sequence number (auto-increments for each frame)
|
||||||
|
_f = None # File handle for debug AMBE binary output
|
||||||
|
|
||||||
|
_tx_tg = hex_str_3(9998) # Hard code the destination TG. This ensures traffic will not show up on DMR-MARC
|
||||||
|
_tx_ts = 2 # Time Slot 2
|
||||||
|
_currentNetwork = ""
|
||||||
|
_dmrgui = ''
|
||||||
|
|
||||||
|
###### DEBUGDEBUGDEBUG
|
||||||
|
#_d = None
|
||||||
|
###### DEBUGDEBUGDEBUG
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
IPSC.__init__(self, *args, **kwargs)
|
IPSC.__init__(self, *args, **kwargs)
|
||||||
self.CALL_DATA = []
|
self.CALL_DATA = []
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Define default values for operation. These will be overridden by the .cfg file if found
|
# Define default values for operation. These will be overridden by the .cfg file if found
|
||||||
#
|
#
|
||||||
|
|
||||||
self._currentTG = self._no_tg
|
self._currentTG = self._no_tg
|
||||||
self._sequenceNr = 0
|
self._currentNetwork = str(args[0])
|
||||||
self.readConfigFile(self._configFile)
|
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
||||||
|
|
||||||
print('DMRLink ambe server')
|
logger.info('DMRLink ambe server')
|
||||||
|
if self._gateway_dmr_id == 0:
|
||||||
|
sys.exit( "Error: gatewayDmrId must be set (greater than zero)" )
|
||||||
#
|
#
|
||||||
# Open output sincs
|
# Open output sincs
|
||||||
#
|
#
|
||||||
if self._outToFile == True:
|
if self._outToFile == True:
|
||||||
f = open('ambe.bin', 'wb')
|
self._f = open('ambe.bin', 'wb')
|
||||||
print('Opening output file: ambe.bin')
|
logger.info('Opening output file: ambe.bin')
|
||||||
if self._outToUDP == True:
|
if self._outToUDP == True:
|
||||||
self._sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
self._sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||||
print('Send UDP frames to DMR gateway {}:{}'.format(self._gateway, self._gateway_port))
|
logger.info('Send UDP frames to DMR gateway {}:{}'.format(self._gateway, self._gateway_port))
|
||||||
|
|
||||||
|
###### DEBUGDEBUGDEBUG
|
||||||
|
#self._d = open('recordData.bin', 'wb')
|
||||||
|
###### DEBUGDEBUGDEBUG
|
||||||
|
|
||||||
try:
|
try:
|
||||||
thread.start_new_thread( self.remote_control, (self._remote_control_port, ) )
|
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
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
print( "Error: unable to start thread" )
|
logger.error( "Error: unable to start thread" )
|
||||||
|
|
||||||
|
|
||||||
# Utility function to convert bytes to string of hex values (for debug)
|
# Utility function to convert bytes to string of hex values (for debug)
|
||||||
def ByteToHex( self, byteStr ):
|
def ByteToHex( self, byteStr ):
|
||||||
@ -92,33 +122,264 @@ class ambeIPSC(IPSC):
|
|||||||
#
|
#
|
||||||
# Now read the configuration file and parse out the values we need
|
# Now read the configuration file and parse out the values we need
|
||||||
#
|
#
|
||||||
def readConfigFile(self, configFileName):
|
def defaultOption( self, config, sec, opt, defaultValue ):
|
||||||
|
try:
|
||||||
|
_value = config.get(sec, opt).split(None)[0] # Get the value from the named section
|
||||||
|
except ConfigParser.NoOptionError as e:
|
||||||
|
try:
|
||||||
|
_value = config.get('DEFAULTS', opt).split(None)[0] # Try the global DEFAULTS section
|
||||||
|
except ConfigParser.NoOptionError as e:
|
||||||
|
_value = defaultValue # Not found anywhere, use the default value
|
||||||
|
logger.info(opt + ' = ' + str(_value))
|
||||||
|
return _value
|
||||||
|
|
||||||
|
def readConfigFile(self, configFileName, sec, networkName='DEFAULTS'):
|
||||||
config = ConfigParser.ConfigParser()
|
config = ConfigParser.ConfigParser()
|
||||||
try:
|
try:
|
||||||
self._tg_filter=[]
|
|
||||||
config.read(configFileName)
|
config.read(configFileName)
|
||||||
for sec in config.sections():
|
|
||||||
for key, val in config.items(sec):
|
if sec == None:
|
||||||
if self._debug == True:
|
sec = self.defaultOption(config, 'DEFAULTS', 'section', networkName)
|
||||||
print( '%s="%s"' % (key, val) )
|
if config.has_section(sec) == False:
|
||||||
self._debug = (config.get(sec, '_debug') == "True")
|
logger.error('Section ' + sec + ' was not found, using DEFAULTS')
|
||||||
self._outToFile = (config.get(sec, '_outToFile') == "True")
|
sec = 'DEFAULTS'
|
||||||
self._outToUDP = (config.get(sec, '_outToUDP') == "True")
|
self._debug = bool(self.defaultOption(config, sec,'debug', self._debug) == 'True')
|
||||||
self._gateway = config.get(sec, '_gateway')
|
self._outToFile = bool(self.defaultOption(config, sec,'outToFile', self._outToFile) == 'True')
|
||||||
self._gateway_port = int(config.get(sec, '_gateway_port'))
|
self._outToUDP = bool(self.defaultOption(config, sec,'outToUDP', self._outToUDP) == 'True')
|
||||||
_tgs = config.get(sec, '_tg_filter')
|
|
||||||
self._tg_filter = map(int, _tgs.split(','))
|
|
||||||
|
|
||||||
|
self._gateway = self.defaultOption(config, sec,'gateway', self._gateway)
|
||||||
|
self._gateway_port = int(self.defaultOption(config, sec,'toGatewayPort', self._gateway_port))
|
||||||
|
|
||||||
|
self._remote_control_port = int(self.defaultOption(config, sec,'remoteControlPort', self._remote_control_port))
|
||||||
|
self._ambeRxPort = int(self.defaultOption(config, sec,'fromGatewayPort', self._ambeRxPort))
|
||||||
|
self._gateway_dmr_id = int(self.defaultOption(config, sec, 'gatewayDmrId', self._gateway_dmr_id))
|
||||||
|
|
||||||
|
_tgs = self.defaultOption(config, sec,'tgFilter', str(self._tg_filter).strip('[]'))
|
||||||
|
self._tg_filter = map(int, _tgs.split(','))
|
||||||
|
|
||||||
|
self._tx_tg = hex_str_3(int(self.defaultOption(config, sec, 'txTg', int_id(self._tx_tg))))
|
||||||
|
self._tx_ts = int(self.defaultOption(config, sec, 'txTs', self._tx_ts))
|
||||||
|
|
||||||
|
except ConfigParser.NoOptionError as e:
|
||||||
|
print('Using a default value:', e)
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
||||||
|
|
||||||
|
def rewriteFrame( self, _frame, _network, _newSlot, _newGroup, _newSouceID, _newPeerID ):
|
||||||
|
|
||||||
|
_peerid = _frame[1:5] # int32 peer who is sending us a packet
|
||||||
|
_src_sub = _frame[6:9] # int32 Id of source
|
||||||
|
_burst_data_type = _frame[30]
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
# re-Write the peer radio ID to that of this program
|
||||||
|
_frame = _frame.replace(_peerid, _newPeerID)
|
||||||
|
# re-Write the source subscriber ID to that of this program
|
||||||
|
_frame = _frame.replace(_src_sub, _newSouceID)
|
||||||
|
# Re-Write the destination Group ID
|
||||||
|
_frame = _frame.replace(_frame[9:12], _newGroup)
|
||||||
|
|
||||||
|
# Re-Write IPSC timeslot value
|
||||||
|
_call_info = int_id(_frame[17:18])
|
||||||
|
if _newSlot == 1:
|
||||||
|
_call_info &= ~(1 << 5)
|
||||||
|
elif _newSlot == 2:
|
||||||
|
_call_info |= 1 << 5
|
||||||
|
_call_info = chr(_call_info)
|
||||||
|
_frame = _frame[:17] + _call_info + _frame[18:]
|
||||||
|
|
||||||
|
_x = struct.pack("i", self._seq)
|
||||||
|
_frame = _frame[:20] + _x[1] + _x[0] + _frame[22:]
|
||||||
|
self._seq = self._seq + 1
|
||||||
|
|
||||||
|
# Re-Write DMR timeslot value
|
||||||
|
# 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 _newSlot == 1:
|
||||||
|
_burst_data_type = BURST_DATA_TYPE['SLOT1_VOICE']
|
||||||
|
elif _newSlot == 2:
|
||||||
|
_burst_data_type = BURST_DATA_TYPE['SLOT2_VOICE']
|
||||||
|
_frame = _frame[:30] + _burst_data_type + _frame[31:]
|
||||||
|
|
||||||
|
_frame = self.hashed_packet(NETWORK[_network]['LOCAL']['AUTH_KEY'], _frame)
|
||||||
|
|
||||||
|
if (time() - self._busy_slots[_newSlot]) >= 0.10 : # slot is not busy so it is safe to transmit
|
||||||
|
# Send the packet to all peers in the target IPSC
|
||||||
|
self.send_to_ipsc(_frame)
|
||||||
|
else:
|
||||||
|
logger.info('Slot {} is busy, will not transmit packet from gateway'.format(_newSlot))
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# Read a record from the captured IPSC file looking for a payload type that matches the filter
|
||||||
|
def readRecord(self, _file, _match_type):
|
||||||
|
_notEOF = True
|
||||||
|
# _file.seek(0)
|
||||||
|
while (_notEOF):
|
||||||
|
_data = ""
|
||||||
|
_bLen = _file.read(4)
|
||||||
|
if _bLen:
|
||||||
|
_len, = struct.unpack("i", _bLen)
|
||||||
|
if _len > 0:
|
||||||
|
_data = _file.read(_len)
|
||||||
|
_payload_type = _data[30]
|
||||||
|
if _payload_type == _match_type:
|
||||||
|
return _data
|
||||||
|
else:
|
||||||
|
_notEOF = False
|
||||||
|
else:
|
||||||
|
_notEOF = False
|
||||||
|
return _data
|
||||||
|
|
||||||
|
# Read bytes from the socket with "timeout" I hate this code.
|
||||||
|
def readSock( self, _sock, len ):
|
||||||
|
counter = 0
|
||||||
|
while(counter < 3):
|
||||||
|
_ambe = _sock.recv(len)
|
||||||
|
if _ambe: break
|
||||||
|
sleep(0.1)
|
||||||
|
counter = counter + 1
|
||||||
|
return _ambe
|
||||||
|
|
||||||
|
# Concatenate 3 frames from the stream into a bit array and return the bytes
|
||||||
|
def readAmbeFrameFromUDP( self, _sock ):
|
||||||
|
_ambeAll = BitArray() # Start with an empty array
|
||||||
|
for i in range(0, 3):
|
||||||
|
_ambe = self.readSock(_sock,7) # Read AMBE from the socket
|
||||||
|
if _ambe:
|
||||||
|
_ambe1 = BitArray('0x'+h(_ambe[0:49]))
|
||||||
|
_ambeAll += _ambe1[0:50] # Append the 49 bits to the string
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
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):
|
||||||
|
s = socket.socket() # Create a socket object
|
||||||
|
s.bind(('', self._ambeRxPort)) # Bind to the port
|
||||||
|
|
||||||
|
while (1): # Forever!
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
self.transmitDisabled(_sock, _network) #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):
|
||||||
|
_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
|
||||||
|
|
||||||
|
logger.info('Transmit from gateway to TG {}:'.format(int_id(self._tx_tg)) )
|
||||||
|
try:
|
||||||
|
|
||||||
|
try:
|
||||||
|
_t = open('template.bin', 'rb') # Open the template file. This was recorded OTA
|
||||||
|
|
||||||
|
_tempHead = [0] * 3 # It appears that there 3 frames of HEAD (mostly the same)
|
||||||
|
for i in range(0, 3):
|
||||||
|
_tempHead[i] = self.readRecord(_t, BURST_DATA_TYPE['VOICE_HEAD'])
|
||||||
|
|
||||||
|
_tempVoice = [0] * 6
|
||||||
|
for i in range(0, 6): # Then there are 6 frames of AMBE. We will just use them in order
|
||||||
|
_tempVoice[i] = self.readRecord(_t, BURST_DATA_TYPE['SLOT2_VOICE'])
|
||||||
|
|
||||||
|
_tempTerm = self.readRecord(_t, BURST_DATA_TYPE['VOICE_TERM'])
|
||||||
|
_t.close()
|
||||||
|
except IOError:
|
||||||
|
logger.error('Can not open template.bin file')
|
||||||
|
return
|
||||||
|
logger.debug('IPSC templates loaded')
|
||||||
|
|
||||||
|
_eof = False
|
||||||
|
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])
|
||||||
|
sleep(_delay)
|
||||||
|
|
||||||
|
i = 0 # Initialize the VOICE template index
|
||||||
|
while(_eof == False):
|
||||||
|
_ambe = self.readAmbeFrameFromUDP(_sock) # Read the 49*3 bit sample from the stream
|
||||||
|
if _ambe:
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
except IOError:
|
||||||
|
logger.error('Can not transmit to peers')
|
||||||
|
logger.info('Transmit complete')
|
||||||
|
|
||||||
|
def transmitDisabled(self, _sock, _network):
|
||||||
|
_eof = False
|
||||||
|
logger.debug('Transmit disabled begin')
|
||||||
|
while(_eof == False):
|
||||||
|
if self.readAmbeFrameFromUDP(_sock):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_eof = True # There are no more AMBE frames, so terminate the loop
|
||||||
|
logger.debug('Transmit disabled end')
|
||||||
|
|
||||||
|
# Debug method used to test the AMBE code.
|
||||||
|
def playbackFromFile(self, _fileName):
|
||||||
|
_r = open(_fileName, 'rb')
|
||||||
|
_eof = False
|
||||||
|
|
||||||
|
host = socket.gethostbyname(socket.gethostname()) # Get local machine name
|
||||||
|
_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
_sock.connect((host, self._ambeRxPort))
|
||||||
|
|
||||||
|
while(_eof == False):
|
||||||
|
|
||||||
|
for i in range(0, 3):
|
||||||
|
_ambe = _r.read(7)
|
||||||
|
if _ambe:
|
||||||
|
_sock.send(_ambe)
|
||||||
|
else:
|
||||||
|
_eof = True
|
||||||
|
sleep(0.055)
|
||||||
|
logger.info('File playback complete')
|
||||||
|
|
||||||
|
def dumpTemplate(self, _fileName):
|
||||||
|
_file = open(_fileName, 'rb')
|
||||||
|
_eof = False
|
||||||
|
|
||||||
|
while(_eof == False):
|
||||||
|
_data = ""
|
||||||
|
_bLen = _file.read(4)
|
||||||
|
if _bLen:
|
||||||
|
_len, = struct.unpack("i", _bLen)
|
||||||
|
if _len > 0:
|
||||||
|
_data = _file.read(_len)
|
||||||
|
self.dumpIPSCFrame(_data)
|
||||||
|
else:
|
||||||
|
_eof = True
|
||||||
|
logger.info('File dump complete')
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||||
#************************************************
|
#************************************************
|
||||||
#
|
#
|
||||||
|
|
||||||
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
def group_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
|
|
||||||
|
#self.dumpIPSCFrame(_data)
|
||||||
|
|
||||||
# THIS FUNCTION IS NOT COMPLETE!!!!
|
# THIS FUNCTION IS NOT COMPLETE!!!!
|
||||||
_payload_type = _data[30:31]
|
_payload_type = _data[30:31]
|
||||||
# _ambe_frames = _data[33:52]
|
# _ambe_frames = _data[33:52]
|
||||||
@ -128,91 +389,222 @@ class ambeIPSC(IPSC):
|
|||||||
_ambe_frame3 = _ambe_frames[100:149]
|
_ambe_frame3 = _ambe_frames[100:149]
|
||||||
|
|
||||||
_tg_id = int_id(_dst_sub)
|
_tg_id = int_id(_dst_sub)
|
||||||
|
_ts = 2 if _ts else 1
|
||||||
|
|
||||||
|
self._busy_slots[_ts] = time()
|
||||||
|
|
||||||
|
###### DEBUGDEBUGDEBUG
|
||||||
|
# if _tg_id == 2:
|
||||||
|
# __iLen = len(_data)
|
||||||
|
# self._d.write(struct.pack("i", __iLen))
|
||||||
|
# self._d.write(_data)
|
||||||
|
# else:
|
||||||
|
# self.rewriteFrame(_data, _network, 1, 9)
|
||||||
|
###### DEBUGDEBUGDEBUG
|
||||||
|
|
||||||
|
|
||||||
if _tg_id in self._tg_filter: #All TGs
|
if _tg_id in self._tg_filter: #All TGs
|
||||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
||||||
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
if self._currentTG == self._no_tg:
|
if self._currentTG == self._no_tg:
|
||||||
_src_sub = get_info(int_id(_src_sub), subscriber_ids)
|
_src_sub = get_subscriber_info(_src_sub)
|
||||||
print('Voice Transmission Start on TS {} and TG {} ({}) from {}'.format("2" if _ts else "1", _dst_sub, _tg_id, _src_sub))
|
logger.info('Voice Transmission Start on TS {} and TG {} ({}) from {}'.format(_ts, _dst_sub, _tg_id, _src_sub))
|
||||||
|
self._sock.sendto('reply log2 {} {}'.format(_src_sub, _tg_id), (self._dmrgui, 34003))
|
||||||
|
|
||||||
self._currentTG = _tg_id
|
self._currentTG = _tg_id
|
||||||
self._transmitStartTime = time()
|
self._transmitStartTime = time()
|
||||||
|
self._start_seq = int_id(_data[20:22])
|
||||||
|
self._packet_count = 0
|
||||||
else:
|
else:
|
||||||
if self._currentTG != _tg_id:
|
if self._currentTG != _tg_id:
|
||||||
if time() > self.lastPacketTimeout:
|
if time() > self.lastPacketTimeout:
|
||||||
self._currentTG = self._no_tg #looks like we never saw an EOT from the last stream
|
self._currentTG = self._no_tg #looks like we never saw an EOT from the last stream
|
||||||
print('EOT timeout')
|
logger.warning('EOT timeout')
|
||||||
else:
|
else:
|
||||||
print('Transmission in progress, will not decode stream on TG {}'.format(_tg_id))
|
logger.warning('Transmission in progress, will not decode stream on TG {}'.format(_tg_id))
|
||||||
if self._currentTG == _tg_id:
|
if self._currentTG == _tg_id:
|
||||||
if _payload_type == BURST_DATA_TYPE['VOICE_TERM']:
|
if _payload_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||||
print('Voice Transmission End %.2f seconds' % (time() - self._transmitStartTime))
|
_source_packets = ( int_id(_data[20:22]) - self._start_seq ) - 3 # the 3 is because the start and end are not part of the voice but counted in the RTP
|
||||||
|
if self._packet_count > _source_packets:
|
||||||
|
self._packet_count = _source_packets
|
||||||
|
if _source_packets > 0:
|
||||||
|
_lost_percentage = 100.0 - ((self._packet_count / float(_source_packets)) * 100.0)
|
||||||
|
else:
|
||||||
|
_lost_percentage = 0.0
|
||||||
|
_duration = (time() - self._transmitStartTime)
|
||||||
|
logger.info('Voice Transmission End {:.2f} seconds loss rate: {:.2f}% ({}/{})'.format(_duration, _lost_percentage, _source_packets - self._packet_count, _source_packets))
|
||||||
|
self._sock.sendto("reply log" +
|
||||||
|
strftime(" %m/%d/%y %H:%M:%S", localtime(self._transmitStartTime)) +
|
||||||
|
' {} {} "{}"'.format(get_subscriber_info(_src_sub), _ts, _dst_sub) +
|
||||||
|
' {:.2f}%'.format(_lost_percentage) +
|
||||||
|
' {:.2f}s'.format(_duration), (self._dmrgui, 34003))
|
||||||
self._currentTG = self._no_tg
|
self._currentTG = self._no_tg
|
||||||
if _payload_type == BURST_DATA_TYPE['SLOT1_VOICE']:
|
if _payload_type == BURST_DATA_TYPE['SLOT1_VOICE']:
|
||||||
self.outputFrames(_ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3)
|
self.outputFrames(_ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3)
|
||||||
|
self._packet_count += 1
|
||||||
if _payload_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
if _payload_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
||||||
self.outputFrames(_ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3)
|
self.outputFrames(_ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3)
|
||||||
|
self._packet_count += 1
|
||||||
self.lastPacketTimeout = time() + 10
|
self.lastPacketTimeout = time() + 10
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
_dst_sub = get_info(int_id(_dst_sub), talkgroup_ids)
|
||||||
print('Ignored Voice Transmission Start on TS {} and TG {}'.format("2" if _ts else "1", _dst_sub))
|
logger.warning('Ignored Voice Transmission Start on TS {} and TG {}'.format(_ts, _dst_sub))
|
||||||
|
|
||||||
def outputFrames(self, _ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3):
|
def outputFrames(self, _ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3):
|
||||||
if self._debug == True:
|
if self._debug == True:
|
||||||
print(_ambe_frames)
|
logger.debug(_ambe_frames)
|
||||||
print('Frame 1:', self.ByteToHex(_ambe_frame1.tobytes()))
|
logger.debug('Frame 1:', self.ByteToHex(_ambe_frame1.tobytes()))
|
||||||
print('Frame 2:', self.ByteToHex(_ambe_frame2.tobytes()))
|
logger.debug('Frame 2:', self.ByteToHex(_ambe_frame2.tobytes()))
|
||||||
print('Frame 3:', self.ByteToHex(_ambe_frame3.tobytes()))
|
logger.debug('Frame 3:', self.ByteToHex(_ambe_frame3.tobytes()))
|
||||||
|
|
||||||
if self._outToFile == True:
|
if self._outToFile == True:
|
||||||
f.write( _ambe_frame1.tobytes() )
|
self._f.write( _ambe_frame1.tobytes() )
|
||||||
f.write( _ambe_frame2.tobytes() )
|
self._f.write( _ambe_frame2.tobytes() )
|
||||||
f.write( _ambe_frame3.tobytes() )
|
self._f.write( _ambe_frame3.tobytes() )
|
||||||
|
|
||||||
if self._outToUDP == True:
|
if self._outToUDP == True:
|
||||||
self._sock.sendto(_ambe_frame1.tobytes(), (self._gateway, self._gateway_port))
|
self._sock.sendto(_ambe_frame1.tobytes(), (self._gateway, self._gateway_port))
|
||||||
self._sock.sendto(_ambe_frame2.tobytes(), (self._gateway, self._gateway_port))
|
self._sock.sendto(_ambe_frame2.tobytes(), (self._gateway, self._gateway_port))
|
||||||
self._sock.sendto(_ambe_frame3.tobytes(), (self._gateway, self._gateway_port))
|
self._sock.sendto(_ambe_frame3.tobytes(), (self._gateway, self._gateway_port))
|
||||||
|
|
||||||
|
def private_voice(self, _network, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||||
def reread_subscribers(self):
|
print('private voice')
|
||||||
try:
|
# __iLen = len(_data)
|
||||||
with open(PATH+'subscriber_ids.csv', 'rU') as subscriber_ids_csv:
|
# self._d.write(struct.pack("i", __iLen))
|
||||||
subscribers = csv.reader(subscriber_ids_csv, dialect='excel', delimiter=',')
|
# self._d.write(_data)
|
||||||
subscriber_ids = {}
|
|
||||||
for row in subscribers:
|
|
||||||
subscriber_ids[int(row[0])] = (row[1])
|
|
||||||
print('Subscriber file has been updated')
|
|
||||||
except ImportError:
|
|
||||||
logger.warning('subscriber_ids.csv not found: Subscriber aliases will not be available')
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Define a function for the thread
|
# Remote control thread
|
||||||
# Use netcat to dynamically change the TGs that are forwarded to Allstar
|
# Use netcat to dynamically change ambe_audio without a restart
|
||||||
# echo -n "x,y,z" | nc 127.0.0.1 1235
|
# echo -n "tgs=x,y,z" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "reread_subscribers" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "reread_config" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "txTg=##" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "txTs=#" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "section=XX" | nc 127.0.0.1 31002
|
||||||
#
|
#
|
||||||
def remote_control(self, port):
|
def remote_control(self, port):
|
||||||
s = socket.socket() # Create a socket object
|
s = socket.socket() # Create a socket object
|
||||||
host = socket.gethostname() # Get local machine name
|
|
||||||
s.bind((host, port)) # Bind to the port
|
|
||||||
|
|
||||||
|
s.bind(('', port)) # Bind to the port
|
||||||
s.listen(5) # Now wait for client connection.
|
s.listen(5) # Now wait for client connection.
|
||||||
print('Remote control is listening on:', host, port)
|
logger.info('Remote control is listening on {}:{}'.format(socket.getfqdn(), port))
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
c, addr = s.accept() # Establish connection with client.
|
c, addr = s.accept() # Establish connection with client.
|
||||||
print( 'Got connection from', addr )
|
logger.info( 'Got connection from {}'.format(addr) )
|
||||||
tgs = c.recv(1024)
|
self._dmrgui = addr[0]
|
||||||
print('Command:"{}"'.format(tgs))
|
_tmp = c.recv(1024)
|
||||||
if tgs:
|
_tmp = _tmp.split(None)[0] #first get rid of whitespace
|
||||||
if tgs == 'reread_subscribers':
|
_cmd = _tmp.split('=')[0]
|
||||||
self.reread_subscribers()
|
logger.info('Command:"{}"'.format(_cmd))
|
||||||
|
if _cmd:
|
||||||
|
if _cmd == 'reread_subscribers':
|
||||||
|
reread_subscribers()
|
||||||
|
elif _cmd == 'reread_config':
|
||||||
|
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
||||||
|
elif _cmd == 'txTg':
|
||||||
|
self._tx_tg = hex_str_3(int(_tmp.split('=')[1]))
|
||||||
|
print('New txTg = ' + str(int_id(self._tx_tg)))
|
||||||
|
elif _cmd == 'txTs':
|
||||||
|
self._tx_ts = int(_tmp.split('=')[1])
|
||||||
|
print('New txTs = ' + str(self._tx_ts))
|
||||||
|
elif _cmd == 'section':
|
||||||
|
self.readConfigFile(self._configFile, _tmp.split('=')[1])
|
||||||
|
elif _cmd == 'gateway_dmr_id':
|
||||||
|
self._gateway_dmr_id = int(_tmp.split('=')[1])
|
||||||
|
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)
|
||||||
|
print('New peer_id = ' + str(peerID))
|
||||||
|
elif _cmd == 'restart':
|
||||||
|
reactor.callFromThread(reactor.stop)
|
||||||
|
elif _cmd == 'playbackFromFile':
|
||||||
|
self.playbackFromFile('ambe.bin')
|
||||||
|
elif _cmd == 'tgs':
|
||||||
|
_args = _tmp.split('=')[1]
|
||||||
|
self._tg_filter = map(int, _args.split(','))
|
||||||
|
logger.info( 'New TGs={}'.format(self._tg_filter) )
|
||||||
|
elif _cmd == 'dump_template':
|
||||||
|
self.dumpTemplate('PrivateVoice.bin')
|
||||||
|
elif _cmd == 'get_info':
|
||||||
|
self._sock.sendto('reply dmr_info {} {} {} {}'.format(self._currentNetwork,
|
||||||
|
int_id(NETWORK[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':
|
||||||
|
_sz = len(_tmp)-5
|
||||||
|
_evalExpression = _tmp[-_sz:]
|
||||||
|
_evalResult = eval(_evalExpression)
|
||||||
|
print("eval of {} is {}".format(_evalExpression, _evalResult))
|
||||||
|
self._sock.sendto('reply eval {}'.format(_evalResult), (self._dmrgui, 34003))
|
||||||
|
elif _cmd == 'exec':
|
||||||
|
_sz = len(_tmp)-5
|
||||||
|
_evalExpression = _tmp[-_sz:]
|
||||||
|
exec(_evalExpression)
|
||||||
|
print("exec of {}".format(_evalExpression))
|
||||||
else:
|
else:
|
||||||
self._tg_filter = map(int, tgs.split(','))
|
logger.error('Unknown command')
|
||||||
print( 'New TGs=', self._tg_filter )
|
|
||||||
c.close() # Close the connection
|
c.close() # Close the connection
|
||||||
|
|
||||||
|
|
||||||
|
#************************************************
|
||||||
|
# Debug: print IPSC frame on console
|
||||||
|
#************************************************
|
||||||
|
def dumpIPSCFrame( self, _frame ):
|
||||||
|
|
||||||
|
_packettype = int_id(_frame[0:1]) # int8 GROUP_VOICE, PVT_VOICE, GROUP_DATA, PVT_DATA, CALL_MON_STATUS, CALL_MON_RPT, CALL_MON_NACK, XCMP_XNL, RPT_WAKE_UP, DE_REG_REQ
|
||||||
|
_peerid = int_id(_frame[1:5]) # int32 peer who is sending us a packet
|
||||||
|
_ipsc_seq = int_id(_frame[5:6]) # int8 looks like a sequence number for a packet
|
||||||
|
_src_sub = int_id(_frame[6:9]) # int32 Id of source
|
||||||
|
_dst_sub = int_id(_frame[9:12]) # int32 Id of destination
|
||||||
|
_call_type = int_id(_frame[12:13]) # int8 Priority Voice/Data
|
||||||
|
_call_ctrl_info = int_id(_frame[13:17]) # int32
|
||||||
|
_call_info = int_id(_frame[17:18]) # int8 Bits 6 and 7 defined as TS and END
|
||||||
|
|
||||||
|
# parse out the RTP values
|
||||||
|
_rtp_byte_1 = int_id(_frame[18:19]) # Call Ctrl Src
|
||||||
|
_rtp_byte_2 = int_id(_frame[19:20]) # Type
|
||||||
|
_rtp_seq = int_id(_frame[20:22]) # Call Seq No
|
||||||
|
_rtp_tmstmp = int_id(_frame[22:26]) # Timestamp
|
||||||
|
_rtp_ssid = int_id(_frame[26:30]) # Sync Src Id
|
||||||
|
|
||||||
|
_payload_type = _frame[30] # int8 VOICE_HEAD, VOICE_TERM, SLOT1_VOICE, SLOT2_VOICE
|
||||||
|
|
||||||
|
_ts = bool(_call_info & TS_CALL_MSK)
|
||||||
|
_end = bool(_call_info & END_MSK)
|
||||||
|
|
||||||
|
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||||
|
print('HEAD:', h(_frame))
|
||||||
|
if _payload_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||||
|
|
||||||
|
_ipsc_rssi_threshold_and_parity = int_id(_frame[31])
|
||||||
|
_ipsc_length_to_follow = int_id(_frame[32:34])
|
||||||
|
_ipsc_rssi_status = int_id(_frame[34])
|
||||||
|
_ipsc_slot_type_sync = int_id(_frame[35])
|
||||||
|
_ipsc_data_size = int_id(_frame[36:38])
|
||||||
|
_ipsc_data = _frame[38:38+(_ipsc_length_to_follow * 2)-4]
|
||||||
|
_ipsc_full_lc_byte1 = int_id(_frame[38])
|
||||||
|
_ipsc_full_lc_fid = int_id(_frame[39])
|
||||||
|
_ipsc_voice_pdu_service_options = int_id(_frame[40])
|
||||||
|
_ipsc_voice_pdu_dst = int_id(_frame[41:44])
|
||||||
|
_ipsc_voice_pdu_src = int_id(_frame[44:47])
|
||||||
|
|
||||||
|
print('{} {} {} {} {} {} {} {} {} {} {}'.format(_ipsc_rssi_threshold_and_parity,_ipsc_length_to_follow,_ipsc_rssi_status,_ipsc_slot_type_sync,_ipsc_data_size,h(_ipsc_data),_ipsc_full_lc_byte1,_ipsc_full_lc_fid,_ipsc_voice_pdu_service_options,_ipsc_voice_pdu_dst,_ipsc_voice_pdu_src))
|
||||||
|
print('TERM:', h(_frame))
|
||||||
|
if _payload_type == BURST_DATA_TYPE['SLOT1_VOICE']:
|
||||||
|
_rtp_len = _frame[31:32]
|
||||||
|
_ambe = _frame[33:52]
|
||||||
|
print('SLOT1:', h(_frame))
|
||||||
|
if _payload_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
||||||
|
_rtp_len = _frame[31:32]
|
||||||
|
_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__':
|
if __name__ == '__main__':
|
||||||
logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
logger.info('DMRlink \'ambe_audio.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||||
for ipsc_network in NETWORK:
|
for ipsc_network in NETWORK:
|
||||||
|
31
ambe_audio_commands.txt
Normal file
31
ambe_audio_commands.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
AllStar DTMF command examples:
|
||||||
|
82=cmd,/bin/bash -c 'do something here'
|
||||||
|
82=cmd,/bin/bash -c 'echo -n "section=Shutup" | nc 127.0.0.1 31002'
|
||||||
|
|
||||||
|
Shell command examples:
|
||||||
|
# Use netcat to dynamically change ambe_audio without a restart
|
||||||
|
# echo -n "tgs=x,y,z" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "reread_subscribers" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "reread_config" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "txTg=##" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "txTs=#" | nc 127.0.0.1 31002
|
||||||
|
# echo -n "section=XX" | nc 127.0.0.1 31002
|
||||||
|
|
||||||
|
Remote control commands:
|
||||||
|
'reread_subscribers'
|
||||||
|
'reread_config'
|
||||||
|
'txTg'
|
||||||
|
'txTs'
|
||||||
|
'section'
|
||||||
|
'gateway_dmr_id'
|
||||||
|
'gateway_peer_id'
|
||||||
|
'restart'
|
||||||
|
'playbackFromFile'
|
||||||
|
'tgs'
|
||||||
|
'dump_template'
|
||||||
|
'get_info'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
74
dmrlink.py
74
dmrlink.py
@ -289,30 +289,47 @@ subscriber_ids = {}
|
|||||||
peer_ids = {}
|
peer_ids = {}
|
||||||
talkgroup_ids = {}
|
talkgroup_ids = {}
|
||||||
|
|
||||||
try:
|
def reread_peers():
|
||||||
with open(PATH+'subscriber_ids.csv', 'rU') as subscriber_ids_csv:
|
global peer_ids
|
||||||
subscribers = csv.reader(subscriber_ids_csv, dialect='excel', delimiter=',')
|
try:
|
||||||
for row in subscribers:
|
with open(PATH+'peer_ids.csv', 'rU') as peer_ids_csv:
|
||||||
subscriber_ids[int(row[0])] = (row[1])
|
peers = csv.reader(peer_ids_csv, dialect='excel', delimiter=',')
|
||||||
except ImportError:
|
peer_ids = {}
|
||||||
logger.warning('subscriber_ids.csv not found: Subscriber aliases will not be available')
|
for row in peers:
|
||||||
|
peer_ids[int(row[0])] = (row[1])
|
||||||
|
except ImportError:
|
||||||
|
logger.warning('peer_ids.csv not found: Peer aliases will not be available')
|
||||||
|
|
||||||
try:
|
def reread_talkgroups():
|
||||||
with open(PATH+'peer_ids.csv', 'rU') as peer_ids_csv:
|
global talkgroup_ids
|
||||||
peers = csv.reader(peer_ids_csv, dialect='excel', delimiter=',')
|
try:
|
||||||
for row in peers:
|
with open(PATH+'talkgroup_ids.csv', 'rU') as talkgroup_ids_csv:
|
||||||
peer_ids[int(row[0])] = (row[1])
|
talkgroups = csv.reader(talkgroup_ids_csv, dialect='excel', delimiter=',')
|
||||||
except ImportError:
|
talkgroup_ids = {}
|
||||||
logger.warning('peer_ids.csv not found: Peer aliases will not be available')
|
for row in talkgroups:
|
||||||
|
talkgroup_ids[int(row[1])] = (row[0])
|
||||||
|
except ImportError:
|
||||||
|
logger.warning('talkgroup_ids.csv not found: Talkgroup aliases will not be available')
|
||||||
|
|
||||||
try:
|
|
||||||
with open(PATH+'talkgroup_ids.csv', 'rU') as talkgroup_ids_csv:
|
|
||||||
talkgroups = csv.reader(talkgroup_ids_csv, dialect='excel', delimiter=',')
|
|
||||||
for row in talkgroups:
|
|
||||||
talkgroup_ids[int(row[1])] = (row[0])
|
|
||||||
except ImportError:
|
|
||||||
logger.warning('talkgroup_ids.csv not found: Talkgroup aliases will not be available')
|
|
||||||
|
|
||||||
|
def reread_subscribers():
|
||||||
|
global subscriber_ids
|
||||||
|
try:
|
||||||
|
with open(PATH+'subscriber_ids.csv', 'rU') as subscriber_ids_csv:
|
||||||
|
subscribers = csv.reader(subscriber_ids_csv, dialect='excel', delimiter=',')
|
||||||
|
subscriber_ids = {}
|
||||||
|
for row in subscribers:
|
||||||
|
subscriber_ids[int(row[0])] = (row[1])
|
||||||
|
print('Subscriber file has been updated.', len(subscriber_ids), 'IDs imported')
|
||||||
|
except ImportError:
|
||||||
|
logger.warning('subscriber_ids.csv not found: Subscriber aliases will not be available')
|
||||||
|
|
||||||
|
reread_peers()
|
||||||
|
reread_talkgroups()
|
||||||
|
reread_subscribers()
|
||||||
|
|
||||||
|
def get_subscriber_info(_src_sub):
|
||||||
|
return get_info(int_id(_src_sub), subscriber_ids)
|
||||||
|
|
||||||
#************************************************
|
#************************************************
|
||||||
# UTILITY FUNCTIONS FOR INTERNAL USE
|
# UTILITY FUNCTIONS FOR INTERNAL USE
|
||||||
@ -449,8 +466,8 @@ def process_flags_bytes(_hex_flags):
|
|||||||
'VOICE': _voice,
|
'VOICE': _voice,
|
||||||
'MASTER': _master
|
'MASTER': _master
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Take a received peer list and the network it belongs to, process and populate the
|
# Take a received peer list and the network it belongs to, process and populate the
|
||||||
# data structure in my_ipsc_config with the results, and return a simple list of peers.
|
# data structure in my_ipsc_config with the results, and return a simple list of peers.
|
||||||
#
|
#
|
||||||
@ -601,7 +618,7 @@ if REPORTS['REPORT_NETWORKS'] == 'PICKLE':
|
|||||||
file.close()
|
file.close()
|
||||||
except IOError as detail:
|
except IOError as detail:
|
||||||
logger.error('I/O Error: %s', detail)
|
logger.error('I/O Error: %s', detail)
|
||||||
|
|
||||||
elif REPORTS['REPORT_NETWORKS'] == 'PRINT':
|
elif REPORTS['REPORT_NETWORKS'] == 'PRINT':
|
||||||
def reporting_loop():
|
def reporting_loop():
|
||||||
logger.debug('Periodic Reporting Loop Started (PRINT)')
|
logger.debug('Periodic Reporting Loop Started (PRINT)')
|
||||||
@ -813,7 +830,7 @@ class IPSC(DatagramProtocol):
|
|||||||
if _peerid in self._peers.keys():
|
if _peerid in self._peers.keys():
|
||||||
self._peers[_peerid]['STATUS']['CONNECTED'] = True
|
self._peers[_peerid]['STATUS']['CONNECTED'] = True
|
||||||
logger.info('(%s) Registration Reply From: %s, %s:%s', self._network, int_id(_peerid), self._peers[_peerid]['IP'], self._peers[_peerid]['PORT'])
|
logger.info('(%s) Registration Reply From: %s, %s:%s', self._network, int_id(_peerid), self._peers[_peerid]['IP'], self._peers[_peerid]['PORT'])
|
||||||
|
|
||||||
# OUR MASTER HAS ANSWERED OUR KEEP-ALIVE REQUEST - KEEP TRACK OF IT
|
# OUR MASTER HAS ANSWERED OUR KEEP-ALIVE REQUEST - KEEP TRACK OF IT
|
||||||
def master_alive_reply(self, _peerid):
|
def master_alive_reply(self, _peerid):
|
||||||
self.reset_keep_alive(_peerid)
|
self.reset_keep_alive(_peerid)
|
||||||
@ -1117,14 +1134,15 @@ class IPSC(DatagramProtocol):
|
|||||||
_packettype = data[0:1]
|
_packettype = data[0:1]
|
||||||
_peerid = data[1:5]
|
_peerid = data[1:5]
|
||||||
_ipsc_seq = data[5:6]
|
_ipsc_seq = data[5:6]
|
||||||
|
|
||||||
# AUTHENTICATE THE PACKET
|
# AUTHENTICATE THE PACKET
|
||||||
if not self.validate_auth(self._local['AUTH_KEY'], data):
|
if not self.validate_auth(self._local['AUTH_KEY'], data):
|
||||||
logger.warning('(%s) AuthError: IPSC packet failed authentication. Type %s: Peer: %s, %s:%s', self._network, h(_packettype), int_id(_peerid), host, port)
|
logger.warning('(%s) AuthError: IPSC packet failed authentication. Type %s: Peer: %s, %s:%s', self._network, h(_packettype), int_id(_peerid), host, port)
|
||||||
return
|
# return
|
||||||
|
|
||||||
# REMOVE SHA-1 AUTHENTICATION HASH: WE NO LONGER NEED IT
|
# REMOVE SHA-1 AUTHENTICATION HASH: WE NO LONGER NEED IT
|
||||||
data = self.strip_hash(data)
|
else:
|
||||||
|
data = self.strip_hash(data)
|
||||||
|
|
||||||
# PACKETS THAT WE RECEIVE FROM ANY VALID PEER OR VALID MASTER
|
# PACKETS THAT WE RECEIVE FROM ANY VALID PEER OR VALID MASTER
|
||||||
if _packettype in ANY_PEER_REQUIRED:
|
if _packettype in ANY_PEER_REQUIRED:
|
||||||
|
BIN
template.bin
Normal file
BIN
template.bin
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user