Compare commits
41 Commits
IPSC_Bridg
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a55f73d059 | ||
|
1c684486ca | ||
|
67fd6d62a5 | ||
|
785f44e7e6 | ||
|
c023d4a565 | ||
|
ad399792c9 | ||
|
198278c288 | ||
|
2520f394ed | ||
|
bc59c75eb0 | ||
|
01a3fff754 | ||
|
2e1a4c3a58 | ||
|
59a59e4100 | ||
|
81c3467ec0 | ||
|
25d6bc08d0 | ||
|
e8311b1f54 | ||
|
cd11397416 | ||
|
cdd65d8edf | ||
|
2b63b5c111 | ||
|
3fc0bdc63d | ||
|
f5bc547d4d | ||
|
0acc6042e8 | ||
|
b4ab2d31a3 | ||
|
5950240787 | ||
|
63611f2c6c | ||
|
eb2ffe4ecb | ||
|
bf699dcfbc | ||
|
c6a543527f | ||
|
3279aeb527 | ||
|
f216300539 | ||
|
8e858e48a2 | ||
|
c16d549e94 | ||
|
ae9f71d715 | ||
|
4ac862b93c | ||
|
8fbd7ccf33 | ||
|
b8d1449d2f | ||
|
c8804d7231 | ||
|
7fde9d1ce8 | ||
|
d1143ddb7c | ||
|
0079ad1baa | ||
|
547d6e23ed | ||
|
62fc209b4f |
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
2
DO_NOT_README.md
Normal file → Executable file
2
DO_NOT_README.md
Normal file → Executable file
@ -81,7 +81,7 @@ The following illustrates the communication that a peer (us, for example) has wi
|
||||
^ v | v |
|
||||
| +--------------+ ++-------------+ +---------+ |
|
||||
| NO |Did The Master| YES |Set Keep Alive| |Peer List| NO |
|
||||
+-------------+ Respond ? +---->| Counter To 0 | |Received?+----------+
|
||||
+---------+ Respond ? +---->| Counter To 0 | |Received?+----------+
|
||||
+--------------+ +--------------+ +---------+
|
||||
|
||||
*COMMUNICATION WITH PEERS:*
|
||||
|
@ -1,11 +0,0 @@
|
||||
##################################
|
||||
# IPSC_Bridge configuration file #
|
||||
##################################
|
||||
|
||||
# DEFAULTS - General settings. These values are
|
||||
# inherited in each subsequent section (defined by section value).
|
||||
|
||||
[DEFAULTS]
|
||||
gateway = 127.0.0.1 # IP address of Partner Application (HB_Bridge, Analog_Bridge)
|
||||
fromGatewayPort = 31000 # Port IPSC_Bridge is listening on for data (IPSC_Bridge <--- Partner)
|
||||
toGatewayPort = 31003 # Port Partner is listening on for data (IPSC_Bridge ---> Partner)
|
313
IPSC_Bridge.py
313
IPSC_Bridge.py
@ -1,313 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
###############################################################################
|
||||
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
|
||||
# and
|
||||
# Copyright (C) 2017 Mike Zingman, N4IRR <Not.A.Chance@NoWhere.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
|
||||
###############################################################################
|
||||
# This is a bridge application for IPSC networks. It knows how to export AMBE
|
||||
# frames and metadata to an external program/network. It also knows how to import
|
||||
# AMBE and metadata from an external network and send the DMR frames to IPSC networks.
|
||||
###############################################################################
|
||||
|
||||
from __future__ import print_function
|
||||
from twisted.internet import reactor
|
||||
from binascii import b2a_hex as h
|
||||
from bitstring import BitArray
|
||||
|
||||
import sys, socket, ConfigParser, thread, traceback
|
||||
import cPickle as pickle
|
||||
|
||||
from dmrlink import IPSC, systems, config_reports, reportFactory
|
||||
from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias, get_info
|
||||
|
||||
from time import time, sleep, clock, localtime, strftime
|
||||
import csv
|
||||
import struct
|
||||
from random import randint
|
||||
from dmr_utils import ambe_utils
|
||||
from dmr_utils.ambe_bridge import AMBE_IPSC
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK; Dave Kierzkowski, KD8EYF; Robert Garcia, N5QM; Steve Zingman, N4IRS; Mike Zingman, N4IRR'
|
||||
__license__ = 'GNU GPLv3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
__version__ = '20170620'
|
||||
|
||||
|
||||
try:
|
||||
from ipsc.ipsc_const import *
|
||||
except ImportError:
|
||||
sys.exit('IPSC constants 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,
|
||||
#
|
||||
class ambeIPSC(IPSC):
|
||||
|
||||
_gateway = "127.0.0.1" # IP address of app
|
||||
_gateway_port = 31000 # Port Analog_Bridge is listening on for AMBE frames to decode
|
||||
_ambeRxPort = 31003 # Port to listen on for AMBE frames to transmit to all peers
|
||||
|
||||
_busy_slots = [0,0,0] # Keep track of activity on each slot. Make sure app is polite
|
||||
|
||||
_currentNetwork = ""
|
||||
cc = 1
|
||||
|
||||
###### DEBUGDEBUGDEBUG
|
||||
#_d = None
|
||||
###### DEBUGDEBUGDEBUG
|
||||
|
||||
def __init__(self, _name, _config, _logger, _report):
|
||||
IPSC.__init__(self, _name, _config, _logger, _report)
|
||||
|
||||
#
|
||||
# Define default values for operation. These will be overridden by the .cfg file if found
|
||||
#
|
||||
|
||||
self._configFile=cli_args.BRIDGE_CONFIG_FILE
|
||||
self._currentNetwork = str(_name)
|
||||
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
||||
|
||||
logger.info('DMRLink IPSC Bridge')
|
||||
|
||||
self.ipsc_ambe = AMBE_IPSC(self, _name, _config, _logger, self._ambeRxPort)
|
||||
|
||||
def get_globals(self):
|
||||
return (subscriber_ids, talkgroup_ids, peer_ids)
|
||||
|
||||
def get_repeater_id(self, import_id):
|
||||
return self._config['LOCAL']['RADIO_ID']
|
||||
|
||||
#
|
||||
# Now read the configuration file and parse out the values we need
|
||||
#
|
||||
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()
|
||||
try:
|
||||
config.read(configFileName)
|
||||
|
||||
if sec == None:
|
||||
sec = self.defaultOption(config, 'DEFAULTS', 'section', networkName)
|
||||
if config.has_section(sec) == False:
|
||||
logger.info('Section ' + sec + ' was not found, using DEFAULTS')
|
||||
sec = 'DEFAULTS'
|
||||
|
||||
self._gateway = self.defaultOption(config, sec,'gateway', self._gateway)
|
||||
self._gateway_port = int(self.defaultOption(config, sec,'toGatewayPort', self._gateway_port))
|
||||
|
||||
self._ambeRxPort = int(self.defaultOption(config, sec,'fromGatewayPort', self._ambeRxPort))
|
||||
|
||||
except ConfigParser.NoOptionError as e:
|
||||
print('Using a default value:', e)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
||||
|
||||
#************************************************
|
||||
# CALLBACK FUNCTIONS FOR USER PACKET TYPES
|
||||
#************************************************
|
||||
#
|
||||
|
||||
def group_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
_tx_slot = self.ipsc_ambe.tx[_ts]
|
||||
_payload_type = _data[30:31]
|
||||
_seq = int_id(_data[20:22])
|
||||
_tx_slot.frame_count += 1
|
||||
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
_stream_id = int_id(_data[5:6]) # int8 looks like a sequence number for a packet
|
||||
if (_stream_id != _tx_slot.stream_id):
|
||||
self.ipsc_ambe.begin_call(_ts, _src_sub, _dst_sub, _peerid, self.cc, _seq, _stream_id)
|
||||
_tx_slot.lastSeq = _seq
|
||||
if _payload_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||
self.ipsc_ambe.end_call(_tx_slot)
|
||||
if (_payload_type == BURST_DATA_TYPE['SLOT1_VOICE']) or (_payload_type == BURST_DATA_TYPE['SLOT2_VOICE']):
|
||||
_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]
|
||||
self.ipsc_ambe.export_voice(_tx_slot, _seq, _ambe_frame1.tobytes() + _ambe_frame2.tobytes() + _ambe_frame3.tobytes())
|
||||
|
||||
|
||||
def private_voice(self, _src_sub, _dst_sub, _ts, _end, _peerid, _data):
|
||||
print('private voice')
|
||||
|
||||
#************************************************
|
||||
# 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__':
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
from dmr_utils.utils import try_download, mk_id_dict
|
||||
|
||||
from ipsc.dmrlink_log import config_logging
|
||||
from ipsc.dmrlink_config import build_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)')
|
||||
parser.add_argument('-ll', '--log_level', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
|
||||
parser.add_argument('-lh', '--log_handle', action='store', dest='LOG_HANDLERS', help='Override config file logging handler.')
|
||||
parser.add_argument('-b','--bridge_config', action='store', dest='BRIDGE_CONFIG_FILE', help='/full/path/to/bridgeconfig.cfg (default HB_Bridge.cfg)')
|
||||
cli_args = parser.parse_args()
|
||||
|
||||
# Ensure we have a path for the config file, if one wasn't specified, then use the default (top of file)
|
||||
if not cli_args.CFG_FILE:
|
||||
cli_args.CFG_FILE = os.path.dirname(os.path.abspath(__file__))+'/dmrlink.cfg'
|
||||
|
||||
# Ensure we have a path for the bridge config file, if one wasn't specified, then use the default (top of file)
|
||||
if not cli_args.BRIDGE_CONFIG_FILE:
|
||||
cli_args.BRIDGE_CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/IPSC_Bridge.cfg'
|
||||
|
||||
# Call the external routine to build the configuration dictionary
|
||||
CONFIG = build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
if cli_args.LOG_LEVEL:
|
||||
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
|
||||
if cli_args.LOG_HANDLERS:
|
||||
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
|
||||
logger = config_logging(CONFIG['LOGGER'])
|
||||
|
||||
logger.info('DMRlink \'IPSC_Bridge.py\' (c) 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
logger.info('Version %s', __version__)
|
||||
|
||||
# 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 THE REPORTING LOOP
|
||||
report_server = config_reports(CONFIG, logger, reportFactory)
|
||||
|
||||
# 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, report_server)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][system]['LOCAL']['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['LOCAL']['IP'])
|
||||
|
||||
reactor.run()
|
||||
|
||||
|
0
LICENSE.txt
Normal file → Executable file
0
LICENSE.txt
Normal file → Executable file
5
Retired/README.MD
Executable file
5
Retired/README.MD
Executable file
@ -0,0 +1,5 @@
|
||||
|
||||
**Retired files:**
|
||||
|
||||
The files in this directory are being kept for reference ONLY. They contain routines that may have been of use to someone.
|
||||
Do not try to use these programs as is. They will not work!
|
54
Retired/ambe_audio.cfg
Executable file
54
Retired/ambe_audio.cfg
Executable file
@ -0,0 +1,54 @@
|
||||
################################################
|
||||
# ambe_audio configuration file.
|
||||
################################################
|
||||
|
||||
# DEFAULTS - General settings. These values are
|
||||
# inherited in each subsequent section (defined by section value).
|
||||
|
||||
[DEFAULTS]
|
||||
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
|
||||
|
||||
|
678
Retired/ambe_audio.py
Executable file
678
Retired/ambe_audio.py
Executable file
@ -0,0 +1,678 @@
|
||||
#!/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
|
||||
###############################################################################
|
||||
|
||||
# This is a sample applicaiton that dumps all raw AMBE+2 voice frame data
|
||||
# It is useful for things like, decoding the audio stream with a DVSI dongle, etc.
|
||||
|
||||
from __future__ import print_function
|
||||
from twisted.internet import reactor
|
||||
from binascii import b2a_hex as h
|
||||
from bitstring import BitArray
|
||||
|
||||
import sys, socket, ConfigParser, thread, traceback
|
||||
import cPickle as pickle
|
||||
|
||||
from dmrlink import IPSC, mk_ipsc_systems, systems, reportFactory, build_aliases, config_reports
|
||||
from dmr_utils.utils import int_id, hex_str_3, hex_str_4, get_alias, get_info
|
||||
|
||||
from time import time, sleep, clock, localtime, strftime
|
||||
import csv
|
||||
import struct
|
||||
from random import randint
|
||||
|
||||
__author__ = 'Cortney T. Buffington, N0MJS'
|
||||
__copyright__ = 'Copyright (c) 2013 - 2016 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__credits__ = 'Adam Fast, KC0YLK; Dave Kierzkowski, KD8EYF; Robert Garcia, N5QM; Steve Zingman, N4IRS; Mike Zingman, N4IRR'
|
||||
__license__ = 'GNU GPLv3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
|
||||
|
||||
try:
|
||||
from ipsc.ipsc_const import *
|
||||
except ImportError:
|
||||
sys.exit('IPSC constants 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,
|
||||
#
|
||||
class ambeIPSC(IPSC):
|
||||
|
||||
_configFile='ambe_audio.cfg' # Name of the config file to over-ride these default values
|
||||
_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 = "192.168.1.184"
|
||||
_gateway = "127.0.0.1" # IP address of DMRGateway app
|
||||
_gateway_port = 31000 # Port DMRGateway is listening on for AMBE frames to decode
|
||||
_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
|
||||
|
||||
_no_tg = -99 # Flag (const) that defines a value for "no tg is currently active"
|
||||
_busy_slots = [0,0,0] # Keep track of activity on each slot. Make sure app is polite
|
||||
_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, _name, _config, _logger, _report):
|
||||
IPSC.__init__(self, _name, _config, _logger, _report)
|
||||
self.CALL_DATA = []
|
||||
|
||||
#
|
||||
# Define default values for operation. These will be overridden by the .cfg file if found
|
||||
#
|
||||
|
||||
self._currentTG = self._no_tg
|
||||
self._currentNetwork = str(_name)
|
||||
self.readConfigFile(self._configFile, None, self._currentNetwork)
|
||||
|
||||
logger.info('DMRLink ambe server')
|
||||
if self._gateway_dmr_id == 0:
|
||||
sys.exit( "Error: gatewayDmrId must be set (greater than zero)" )
|
||||
#
|
||||
# Open output sincs
|
||||
#
|
||||
if self._outToFile == True:
|
||||
self._f = open('ambe.bin', 'wb')
|
||||
logger.info('Opening output file: ambe.bin')
|
||||
if self._outToUDP == True:
|
||||
self._sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||
logger.info('Send UDP frames to DMR gateway {}:{}'.format(self._gateway, self._gateway_port))
|
||||
|
||||
###### DEBUGDEBUGDEBUG
|
||||
#self._d = open('recordData.bin', 'wb')
|
||||
###### DEBUGDEBUGDEBUG
|
||||
|
||||
try:
|
||||
thread.start_new_thread( self.remote_control, (self._remote_control_port, ) ) # Listen for remote control commands
|
||||
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" )
|
||||
|
||||
|
||||
# Utility function to convert bytes to string of hex values (for debug)
|
||||
def ByteToHex( self, byteStr ):
|
||||
return ''.join( [ "%02X " % ord(x) for x in byteStr ] ).strip()
|
||||
|
||||
#
|
||||
# Now read the configuration file and parse out the values we need
|
||||
#
|
||||
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()
|
||||
try:
|
||||
config.read(configFileName)
|
||||
|
||||
if sec == None:
|
||||
sec = self.defaultOption(config, 'DEFAULTS', 'section', networkName)
|
||||
if config.has_section(sec) == False:
|
||||
logger.error('Section ' + sec + ' was not found, using DEFAULTS')
|
||||
sec = 'DEFAULTS'
|
||||
self._debug = bool(self.defaultOption(config, sec,'debug', self._debug) == 'True')
|
||||
self._outToFile = bool(self.defaultOption(config, sec,'outToFile', self._outToFile) == 'True')
|
||||
self._outToUDP = bool(self.defaultOption(config, sec,'outToUDP', self._outToUDP) == 'True')
|
||||
|
||||
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:
|
||||
traceback.print_exc()
|
||||
sys.exit('Configuration file \''+configFileName+'\' is not a valid configuration file! Exiting...')
|
||||
|
||||
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
|
||||
_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:]
|
||||
|
||||
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, _name):
|
||||
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) # SSZ was here.
|
||||
else:
|
||||
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):
|
||||
_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 = self._config['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], 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
|
||||
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, 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, 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):
|
||||
_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
|
||||
#************************************************
|
||||
#
|
||||
|
||||
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_frame1 = _ambe_frames[0:49]
|
||||
_ambe_frame2 = _ambe_frames[50:99]
|
||||
_ambe_frame3 = _ambe_frames[100:149]
|
||||
|
||||
_tg_id = int_id(_dst_sub)
|
||||
|
||||
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, 1, 9)
|
||||
###### DEBUGDEBUGDEBUG
|
||||
|
||||
|
||||
if _tg_id in self._tg_filter: #All TGs
|
||||
_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)
|
||||
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._transmitStartTime = time()
|
||||
self._start_seq = int_id(_data[20:22])
|
||||
self._packet_count = 0
|
||||
else:
|
||||
if self._currentTG != _tg_id:
|
||||
if time() > self.lastPacketTimeout:
|
||||
self._currentTG = self._no_tg #looks like we never saw an EOT from the last stream
|
||||
logger.warning('EOT timeout')
|
||||
else:
|
||||
logger.warning('Transmission in progress, will not decode stream on TG {}'.format(_tg_id))
|
||||
if self._currentTG == _tg_id:
|
||||
if _payload_type == BURST_DATA_TYPE['VOICE_TERM']:
|
||||
_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
|
||||
if _payload_type == BURST_DATA_TYPE['SLOT1_VOICE']:
|
||||
self.outputFrames(_ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3)
|
||||
self._packet_count += 1
|
||||
if _payload_type == BURST_DATA_TYPE['SLOT2_VOICE']:
|
||||
self.outputFrames(_ambe_frames, _ambe_frame1, _ambe_frame2, _ambe_frame3)
|
||||
self._packet_count += 1
|
||||
self.lastPacketTimeout = time() + 10
|
||||
|
||||
else:
|
||||
if _payload_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
_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):
|
||||
if self._debug == True:
|
||||
logger.debug(_ambe_frames)
|
||||
logger.debug('Frame 1:', self.ByteToHex(_ambe_frame1.tobytes()))
|
||||
logger.debug('Frame 2:', self.ByteToHex(_ambe_frame2.tobytes()))
|
||||
logger.debug('Frame 3:', self.ByteToHex(_ambe_frame3.tobytes()))
|
||||
|
||||
if self._outToFile == True:
|
||||
self._f.write( _ambe_frame1.tobytes() )
|
||||
self._f.write( _ambe_frame2.tobytes() )
|
||||
self._f.write( _ambe_frame3.tobytes() )
|
||||
|
||||
if self._outToUDP == True:
|
||||
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_frame3.tobytes(), (self._gateway, self._gateway_port))
|
||||
|
||||
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))
|
||||
# self._d.write(_data)
|
||||
|
||||
#
|
||||
# Remote control thread
|
||||
# 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
|
||||
#
|
||||
def remote_control(self, port):
|
||||
s = socket.socket() # Create a socket object
|
||||
|
||||
s.bind(('', port)) # Bind to the port
|
||||
s.listen(5) # Now wait for client connection.
|
||||
logger.info('Remote control is listening on {}:{}'.format(socket.getfqdn(), port))
|
||||
|
||||
while True:
|
||||
c, addr = s.accept() # Establish connection with client.
|
||||
logger.info( 'Got connection from {}'.format(addr) )
|
||||
self._dmrgui = addr[0]
|
||||
_tmp = c.recv(1024)
|
||||
_tmp = _tmp.split(None)[0] #first get rid of whitespace
|
||||
_cmd = _tmp.split('=')[0]
|
||||
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])
|
||||
self._config['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_alias':
|
||||
self._sock.sendto('reply dmr_info {} {} {} {}'.format(self._currentNetwork,
|
||||
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':
|
||||
_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:
|
||||
logger.error('Unknown command')
|
||||
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))
|
||||
|
||||
def get_subscriber_info(_src_sub):
|
||||
return get_info(int_id(_src_sub), subscriber_ids)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
|
||||
from ipsc.dmrlink_config import build_config
|
||||
from ipsc.dmrlink_log import config_logging
|
||||
|
||||
# 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)')
|
||||
parser.add_argument('-ll', '--log_level', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
|
||||
parser.add_argument('-lh', '--log_handle', action='store', dest='LOG_HANDLERS', help='Override config file logging handler.')
|
||||
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 = build_config(cli_args.CFG_FILE)
|
||||
|
||||
# Call the external routing to start the system logger
|
||||
if cli_args.LOG_LEVEL:
|
||||
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
|
||||
if cli_args.LOG_HANDLERS:
|
||||
CONFIG['LOGGER']['LOG_HANDLERS'] = cli_args.LOG_HANDLERS
|
||||
logger = config_logging(CONFIG['LOGGER'])
|
||||
logger.info('DMRlink \'dmrlink.py\' (c) 2013 - 2015 N0MJS & the K0USY Group - SYSTEM STARTING...')
|
||||
|
||||
# Set signal handers so that we can gracefully exit if need be
|
||||
def sig_handler(_signal, _frame):
|
||||
logger.info('*** DMRLINK IS TERMINATING WITH SIGNAL %s ***', str(_signal))
|
||||
for system in systems:
|
||||
systems[system].de_register_self()
|
||||
reactor.stop()
|
||||
|
||||
for sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:
|
||||
signal.signal(sig, sig_handler)
|
||||
|
||||
# INITIALIZE THE REPORTING LOOP
|
||||
report_server = config_reports(CONFIG, logger, reportFactory)
|
||||
|
||||
# Build ID Aliases
|
||||
peer_ids, subscriber_ids, talkgroup_ids, local_ids = build_aliases(CONFIG, logger)
|
||||
|
||||
# INITIALIZE AN IPSC OBJECT (SELF SUSTAINING) FOR EACH CONFIGRUED IPSC
|
||||
systems = mk_ipsc_systems(CONFIG, logger, systems, ambeIPSC, report_server)
|
||||
|
||||
|
||||
|
||||
# INITIALIZATION COMPLETE -- START THE REACTOR
|
||||
reactor.run()
|
31
Retired/ambe_audio_commands.txt
Executable file
31
Retired/ambe_audio_commands.txt
Executable 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'
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -127,16 +127,28 @@ def build_bridges(_known_bridges):
|
||||
# are not yet implemented.
|
||||
def build_acl(_sub_acl):
|
||||
try:
|
||||
logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs')
|
||||
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')
|
||||
ACL_ACTION = acl_file.ACL_ACTION
|
||||
ACL = acl_file.ACL
|
||||
sections = acl_file.ACL.split(':')
|
||||
ACL_ACTION = sections[0]
|
||||
entries_str = sections[1]
|
||||
ACL = set()
|
||||
|
||||
for entry in entries_str.split(','):
|
||||
if '-' in entry:
|
||||
start,end = entry.split('-')
|
||||
start,end = int(start), int(end)
|
||||
for id in range(start, end+1):
|
||||
ACL.add(hex_str_3(id))
|
||||
else:
|
||||
id = int(entry)
|
||||
ACL.add(hex_str_3(id))
|
||||
|
||||
logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL)))
|
||||
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
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
|
||||
|
0
Retired/bridge_rules_SAMPLE.py
Normal file → Executable file
0
Retired/bridge_rules_SAMPLE.py
Normal file → Executable file
0
Retired/known_bridges_SAMPLE.py
Normal file → Executable file
0
Retired/known_bridges_SAMPLE.py
Normal file → Executable file
0
Retired/template.py
Normal file → Executable file
0
Retired/template.py
Normal file → Executable file
@ -137,10 +137,12 @@ def make_bridge_config(_confbridge_rules):
|
||||
_system['ON'][i] = hex_str_3(_system['ON'][i])
|
||||
for i, e in enumerate(_system['OFF']):
|
||||
_system['OFF'][i] = hex_str_3(_system['OFF'][i])
|
||||
for i, e in enumerate(_system['RESET']):
|
||||
_system['RESET'][i] = hex_str_3(_system['RESET'][i])
|
||||
_system['TIMEOUT'] = _system['TIMEOUT']*60
|
||||
_system['TIMER'] = time()
|
||||
|
||||
return {'BRIDGE_CONF': bridge_file.BRIDGE_CONF, 'BRIDGES': bridge_file.BRIDGES}
|
||||
return {'BRIDGE_CONF': bridge_file.BRIDGE_CONF, 'BRIDGES': bridge_file.BRIDGES, 'TRUNKS': bridge_file.TRUNKS}
|
||||
|
||||
|
||||
# Import subscriber ACL
|
||||
@ -148,17 +150,28 @@ def make_bridge_config(_confbridge_rules):
|
||||
# Global action is to allow or deny them. Multiple lists with different actions and ranges
|
||||
# are not yet implemented.
|
||||
def build_acl(_sub_acl):
|
||||
ACL = set()
|
||||
try:
|
||||
logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs')
|
||||
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')
|
||||
ACL_ACTION = acl_file.ACL_ACTION
|
||||
ACL = acl_file.ACL_ACTION
|
||||
sections = acl_file.ACL.split(':')
|
||||
ACL_ACTION = sections[0]
|
||||
entries_str = sections[1]
|
||||
for entry in entries_str.split(','):
|
||||
if '-' in entry:
|
||||
start,end = entry.split('-')
|
||||
start,end = int(start), int(end)
|
||||
for id in range(start, end+1):
|
||||
ACL.add(hex_str_3(id))
|
||||
else:
|
||||
id = int(entry)
|
||||
ACL.add(hex_str_3(id))
|
||||
|
||||
logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL)))
|
||||
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
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
|
||||
@ -240,7 +253,7 @@ class confbridgeIPSC(IPSC):
|
||||
return
|
||||
|
||||
# Process the packet
|
||||
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))
|
||||
#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]
|
||||
|
||||
@ -259,6 +272,8 @@ class confbridgeIPSC(IPSC):
|
||||
|
||||
# BEGIN CONTENTION HANDLING
|
||||
#
|
||||
# If the system is listed as a "TRUNK", there will be no contention handling. All traffic is forwarded to it
|
||||
#
|
||||
# The rules for each of the 4 "ifs" below are listed here for readability. The Frame To Send is:
|
||||
# From a different group than last RX from this IPSC, but it has been less than Group Hangtime
|
||||
# From a different group than last TX to this IPSC, but it has been less than Group Hangtime
|
||||
@ -266,6 +281,7 @@ class confbridgeIPSC(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 _target not in TRUNKS:
|
||||
if ((_target['TGID'] != _target_status[_target['TS']]['RX_TGID']) and ((now - _target_status[_target['TS']]['RX_TIME']) < _target_system['LOCAL']['GROUP_HANGTIME'])):
|
||||
if _burst_data_type == BURST_DATA_TYPE['VOICE_HEAD']:
|
||||
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(_target['TGID']), _target['SYSTEM'], _target['TS'], int_id(_target_status[_target['TS']]['RX_TGID']))
|
||||
@ -291,12 +307,14 @@ class confbridgeIPSC(IPSC):
|
||||
#
|
||||
# Make a copy of the payload
|
||||
_tmp_data = _data
|
||||
# Re-Write the PEER ID in the IPSC Header:
|
||||
_tmp_data = _tmp_data.replace(_peerid, _target_system['LOCAL']['RADIO_ID'], 1)
|
||||
|
||||
# Re-Write the IPSC SRC to match the target network's ID
|
||||
_tmp_data = _tmp_data.replace(_peerid, _target_system['LOCAL']['RADIO_ID'])
|
||||
# Re-Write the IPSC SRC + DST GROUP in IPSC Headers:
|
||||
_tmp_data = _tmp_data.replace(_src_sub + _dst_group, _src_sub + _target['TGID'], 1)
|
||||
|
||||
# Re-Write the destination Group ID
|
||||
_tmp_data = _tmp_data.replace(_dst_group, _target['TGID'])
|
||||
# Re-Write the DST GROUP + IPSC SRC in DMR LC (Header, Terminator and Voice Burst E):
|
||||
_tmp_data = _tmp_data.replace(_dst_group + _src_sub, _target['TGID'] + _src_sub, 1)
|
||||
|
||||
# Re-Write IPSC timeslot value
|
||||
_call_info = int_id(_data[17:18])
|
||||
@ -327,7 +345,6 @@ class confbridgeIPSC(IPSC):
|
||||
# END FRAME FORWARDING
|
||||
#
|
||||
|
||||
|
||||
# Set values for the contention handler to test next time there is a frame to forward
|
||||
_target_status[_target['TS']]['TX_TGID'] = _target['TGID']
|
||||
_target_status[_target['TS']]['TX_TIME'] = now
|
||||
@ -366,14 +383,16 @@ class confbridgeIPSC(IPSC):
|
||||
if self._CONFIG['REPORTS']['REPORT_NETWORKS'] == 'NETWORK':
|
||||
self._report.send_bridgeEvent('GROUP VOICE,UNMATCHED END,{},{},{},{},{},{}'.format(self._system, int_id(_seq_id), int_id(_peerid), int_id(_src_sub), _ts, int_id(_dst_group)))
|
||||
|
||||
|
||||
# Iterate the rules dictionary
|
||||
for _bridge in BRIDGES:
|
||||
for _system in BRIDGES[_bridge]:
|
||||
if _system['SYSTEM'] == self._system:
|
||||
|
||||
# TGID matches an ACTIVATION trigger
|
||||
if _dst_group in _system['ON']:
|
||||
if (_dst_group in _system['ON'] or _dst_group in _system['RESET']) and _ts == _system['TS']:
|
||||
# Set the matching rule as ACTIVE
|
||||
if _dst_group in _system['ON']:
|
||||
if _system['ACTIVE'] == False:
|
||||
_system['ACTIVE'] = True
|
||||
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
|
||||
@ -387,8 +406,9 @@ class confbridgeIPSC(IPSC):
|
||||
self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now)
|
||||
|
||||
# TGID matches an DE-ACTIVATION trigger
|
||||
if _dst_group in _system['OFF']:
|
||||
if (_dst_group in _system['OFF'] or _dst_group in _system['RESET']) and _ts == _system['TS']:
|
||||
# Set the matching rule as ACTIVE
|
||||
if _dst_group in _system['OFF']:
|
||||
if _system['ACTIVE'] == True:
|
||||
_system['ACTIVE'] = False
|
||||
self._logger.info('(%s) Bridge: %s, connection changed to state: %s', self._system, _bridge, _system['ACTIVE'])
|
||||
@ -396,12 +416,12 @@ class confbridgeIPSC(IPSC):
|
||||
if _system['TO_TYPE'] == 'ON':
|
||||
_system['TIMER'] = now
|
||||
self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge)
|
||||
# Reset tge timer for the rule
|
||||
# Reset the timer for the rule
|
||||
if _system['ACTIVE'] == False and _system['TO_TYPE'] == 'OFF':
|
||||
_system['TIMER'] = now + _system['TIMEOUT']
|
||||
self._logger.info('(%s) Bridge: %s, timeout timer reset to: %s', self._system, _bridge, _system['TIMER'] - now)
|
||||
# Cancel the timer if we've enabled an "ON" type timeout
|
||||
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON':
|
||||
if _system['ACTIVE'] == True and _system['TO_TYPE'] == 'ON' and _dst_group in _system['OFF']:
|
||||
_system['TIMER'] = now
|
||||
self._logger.info('(%s) Bridge: %s set to ON with and "OFF" timer rule: timeout timer cancelled', self._system, _bridge)
|
||||
|
||||
@ -478,6 +498,7 @@ if __name__ == '__main__':
|
||||
# Build the routing rules and other configuration
|
||||
CONFIG_DICT = make_bridge_config('confbridge_rules')
|
||||
BRIDGE_CONF = CONFIG_DICT['BRIDGE_CONF']
|
||||
TRUNKS = CONFIG_DICT['TRUNKS']
|
||||
BRIDGES = CONFIG_DICT['BRIDGES']
|
||||
|
||||
# Build the Access Control List
|
||||
|
22
confbridge_rules_SAMPLE.py
Normal file → Executable file
22
confbridge_rules_SAMPLE.py
Normal file → Executable file
@ -19,6 +19,9 @@ configuration file.
|
||||
* ON and OFF are LISTS of Talkgroup IDs used to trigger this system off and on. Even if you
|
||||
only want one (as shown in the ON example), it has to be in list format. None can be
|
||||
handled with an empty list, such as " 'ON': [] ".
|
||||
* RESET is a list of Talkgroup IDs that, in addition to the ON and OFF lists will cause a running
|
||||
timer to be reset. This is useful if you are using different TGIDs for voice traffic than
|
||||
triggering. If you are not, there is NO NEED to use this feature.
|
||||
* TO_TYPE is timeout type. If you want to use timers, ON means when it's turned on, it will
|
||||
turn off afer the timout period and OFF means it will turn back on after the timout
|
||||
period. If you don't want to use timers, set it to anything else, but 'NONE' might be
|
||||
@ -38,18 +41,25 @@ BRIDGE_CONF = {
|
||||
'REPORT': True,
|
||||
}
|
||||
|
||||
# TRUNK IPSC Systems -- trunk bypasses the contention handler and always transmits traffic
|
||||
#
|
||||
# This is a python LIST data type. It needs to be here, but just leave it empty if not used.
|
||||
# The contents are a quoted, comma separated list of IPSC systems that are traffic trunks.
|
||||
# Example: TRUNKS = ['MASTER-1', 'CLIENT-2']
|
||||
TRUNKS = []
|
||||
|
||||
BRIDGES = {
|
||||
'WORLDWIDE': [
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10]},
|
||||
{'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10]},
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 1, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []},
|
||||
{'SYSTEM': 'CLIENT-1', 'TS': 1, 'TGID': 3100, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'ON', 'ON': [2,], 'OFF': [9,10], 'RESET': []}
|
||||
],
|
||||
'ENGLISH': [
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10]},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10]},
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10], 'RESET': []},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 1, 'TGID': 13, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [3,], 'OFF': [8,10], 'RESET': []}
|
||||
],
|
||||
'STATEWIDE': [
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10]},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10]},
|
||||
{'SYSTEM': 'MASTER-1', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10], 'RESET': []},
|
||||
{'SYSTEM': 'CLIENT-2', 'TS': 2, 'TGID': 3129, 'ACTIVE': True, 'TIMEOUT': 2, 'TO_TYPE': 'NONE', 'ON': [4,], 'OFF': [7,10], 'RESET': []}
|
||||
]
|
||||
}
|
||||
|
||||
|
16
dmrlink_SAMPLE.cfg
Normal file → Executable file
16
dmrlink_SAMPLE.cfg
Normal file → Executable file
@ -70,8 +70,8 @@ PRINT_PEERS_INC_FLAGS: 0
|
||||
# used.
|
||||
#
|
||||
[LOGGER]
|
||||
LOG_FILE: /tmp/dmrlink.log
|
||||
LOG_HANDLERS: console-timed,file-timed
|
||||
LOG_FILE: /var/log/dmrlink/dmrlink.log
|
||||
LOG_HANDLERS: file
|
||||
LOG_LEVEL: INFO
|
||||
LOG_NAME: DMRlink
|
||||
|
||||
@ -140,7 +140,7 @@ STALE_DAYS: 7
|
||||
#
|
||||
|
||||
[SAMPLE_PEER]
|
||||
ENABLED: False
|
||||
ENABLED: True
|
||||
RADIO_ID: 12345
|
||||
IP:
|
||||
PORT: 50000
|
||||
@ -151,8 +151,8 @@ IPSC_MODE: DIGITAL
|
||||
TS1_LINK: True
|
||||
TS2_LINK: True
|
||||
CSBK_CALL: False
|
||||
RCM: False
|
||||
CON_APP: False
|
||||
RCM: True
|
||||
CON_APP: True
|
||||
XNL_CALL: False
|
||||
XNL_MASTER: False
|
||||
DATA_CALL: True
|
||||
@ -166,7 +166,7 @@ GROUP_HANGTIME: 5
|
||||
|
||||
|
||||
[SAMPLE_MASTER]
|
||||
ENABLED: True
|
||||
ENABLED: False
|
||||
RADIO_ID: 54321
|
||||
IP: 192.168.1.1
|
||||
PORT: 50000
|
||||
@ -177,8 +177,8 @@ IPSC_MODE: DIGITAL
|
||||
TS1_LINK: True
|
||||
TS2_LINK: True
|
||||
CSBK_CALL: False
|
||||
RCM: False
|
||||
CON_APP: False
|
||||
RCM: True
|
||||
CON_APP: True
|
||||
XNL_CALL: False
|
||||
XNL_MASTER: False
|
||||
DATA_CALL: True
|
||||
|
0
documents/FAQ.md
Normal file → Executable file
0
documents/FAQ.md
Normal file → Executable file
0
documents/internal_data_decode.txt
Normal file → Executable file
0
documents/internal_data_decode.txt
Normal file → Executable file
0
documents/voice_burst_decoding.txt
Normal file → Executable file
0
documents/voice_burst_decoding.txt
Normal file → Executable file
0
documents/voice_packets.txt
Normal file → Executable file
0
documents/voice_packets.txt
Normal file → Executable file
0
ipsc/.gitignore
vendored
Normal file → Executable file
0
ipsc/.gitignore
vendored
Normal file → Executable file
0
ipsc/__init__.py
Normal file → Executable file
0
ipsc/__init__.py
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
###############################################################################
|
||||
# Copyright (C) 2016 Cortney T. Buffington, N0MJS <n0mjs@me.com>
|
||||
# Copyright (C) 2016-2018 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
|
||||
@ -21,16 +21,32 @@
|
||||
import ConfigParser
|
||||
import sys
|
||||
|
||||
from socket import gethostbyname
|
||||
from socket import getaddrinfo, IPPROTO_UDP
|
||||
|
||||
# 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'
|
||||
__copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group'
|
||||
__license__ = 'GNU GPLv3'
|
||||
__maintainer__ = 'Cort Buffington, N0MJS'
|
||||
__email__ = 'n0mjs@me.com'
|
||||
|
||||
|
||||
def get_address(_config):
|
||||
ipv4 = ''
|
||||
ipv6 = ''
|
||||
socket_info = getaddrinfo(_config, None, 0, 0, IPPROTO_UDP)
|
||||
for item in socket_info:
|
||||
if item[0] == 2:
|
||||
ipv4 = item[4][0]
|
||||
elif item[0] == 30:
|
||||
ipv6 = item[4][0]
|
||||
|
||||
if ipv4:
|
||||
return ipv4
|
||||
if ipv6:
|
||||
return ipv6
|
||||
return 'invalid address'
|
||||
|
||||
def build_config(_config_file):
|
||||
config = ConfigParser.ConfigParser()
|
||||
|
||||
@ -115,7 +131,7 @@ def build_config(_config_file):
|
||||
|
||||
# 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')),
|
||||
'IP': config.get(section, 'IP'),
|
||||
'PORT': config.getint(section, 'PORT'),
|
||||
'ALIVE_TIMER': config.getint(section, 'ALIVE_TIMER'),
|
||||
'MAX_MISSED': config.getint(section, 'MAX_MISSED'),
|
||||
@ -144,7 +160,7 @@ def build_config(_config_file):
|
||||
})
|
||||
if not CONFIG['SYSTEMS'][section]['LOCAL']['MASTER_PEER']:
|
||||
CONFIG['SYSTEMS'][section]['MASTER'].update({
|
||||
'IP': gethostbyname(config.get(section, 'MASTER_IP')),
|
||||
'IP': get_address(config.get(section, 'MASTER_IP')),
|
||||
'PORT': config.getint(section, 'MASTER_PORT')
|
||||
})
|
||||
|
||||
@ -219,7 +235,7 @@ if __name__ == '__main__':
|
||||
|
||||
# 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'
|
||||
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/../dmrlink.cfg'
|
||||
|
||||
|
||||
pprint(build_config(cli_args.CONFIG_FILE))
|
||||
|
0
ipsc/ipsc_const.py
Normal file → Executable file
0
ipsc/ipsc_const.py
Normal file → Executable file
0
ipsc/ipsc_mask.py
Normal file → Executable file
0
ipsc/ipsc_mask.py
Normal file → Executable file
0
ipsc/reporting_const.py
Normal file → Executable file
0
ipsc/reporting_const.py
Normal file → Executable file
139
mk-dmrlink
139
mk-dmrlink
@ -1,8 +1,10 @@
|
||||
#! /bin/bash
|
||||
|
||||
currentdir=`pwd`
|
||||
PREFIX=/opt/dmrlink
|
||||
echo "DMRlink will be installed in: $PREFIX"
|
||||
|
||||
echo "Current working directory is" $currentdir
|
||||
currentdir=`pwd`
|
||||
echo "Current working directory is: $currentdir"
|
||||
|
||||
echo ""
|
||||
|
||||
@ -13,17 +15,44 @@ echo ""
|
||||
#################################################
|
||||
|
||||
# Install the required support programs
|
||||
apt-get install unzip -y
|
||||
apt-get install python-dev -y
|
||||
apt-get install python-pip -y
|
||||
apt-get install python-twisted -y
|
||||
|
||||
distro=$(lsb_release -i | awk -F":" '{ gsub(/^[ \t]+/, "", $2); print $2 }')
|
||||
release=$(lsb_release -r | awk -F":" '{ gsub(/^[ \t]+/, "", $2); print $2 }')
|
||||
echo "Current Linux distribution is: $distro $release"
|
||||
|
||||
if [[ "$distro" =~ ^(CentOS|Fedora|openSUSE|)$ ]]; then
|
||||
echo "$distro uses yum"
|
||||
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-$(echo $release | awk -F"." '{print $1}').noarch.rpm
|
||||
yum install -y gcc gcc-c++ glibc-devel make
|
||||
yum install -y unzip
|
||||
yum install -y python-devel
|
||||
yum install -y python-pip
|
||||
yum install -y python-twisted
|
||||
# pip install bitstring
|
||||
# pip install bitarray
|
||||
else
|
||||
echo "$distro uses apt"
|
||||
apt-get install -y build-essential
|
||||
apt-get install -y unzip
|
||||
apt-get install -y python-dev
|
||||
apt-get install -y python-pip
|
||||
apt-get install -y python-twisted
|
||||
# pip install bitstring
|
||||
# pip install bitarray
|
||||
fi
|
||||
|
||||
cd /opt
|
||||
git clone https://github.com/HBLink-org/dmr_utils.git
|
||||
cd dmr_utils/
|
||||
pip install .
|
||||
# Install dmr_utils with pip install
|
||||
pip install dmr_utils
|
||||
###############################################################################
|
||||
# Following lines should be removed due to the pip install method for dmr_utils
|
||||
#cd /opt
|
||||
#if [ ! -d /opt/dmr_utils ]; then
|
||||
# git clone https://github.com/n0mjs710/dmr_utils.git
|
||||
#fi
|
||||
#cd dmr_utils/
|
||||
#git pull
|
||||
#pip install .
|
||||
###############################################################################
|
||||
|
||||
echo "Required programs installed, continuing"
|
||||
|
||||
@ -32,19 +61,13 @@ echo "Required programs installed, continuing"
|
||||
# The needed files are copied to /opt/dmrlink
|
||||
|
||||
# Make needed directories
|
||||
mkdir -p /opt/dmrlink/IPSC_Bridge/
|
||||
# mkdir -p /opt/dmrlink/bridge/
|
||||
mkdir -p /opt/dmrlink/confbridge/
|
||||
# mkdir -p /opt/dmrlink/log/
|
||||
mkdir -p /opt/dmrlink/playback/
|
||||
# mkdir -p /opt/dmrlink/play_group/
|
||||
mkdir -p /opt/dmrlink/proxy/
|
||||
# mkdir -p /opt/dmrlink/rcm/
|
||||
# mkdir -p /opt/dmrlink/record/
|
||||
mkdir -p /opt/dmrlink/samples
|
||||
mkdir -p $PREFIX/confbridge/
|
||||
mkdir -p $PREFIX/playback/
|
||||
mkdir -p $PREFIX/proxy/
|
||||
mkdir -p $PREFIX/samples
|
||||
mkdir -p /var/log/dmrlink
|
||||
|
||||
cd /opt/dmrlink
|
||||
cd $PREFIX
|
||||
|
||||
# Put common files in /opt/dmrlink
|
||||
# cp $currentdir/peer_ids.csv /opt/dmrlink
|
||||
@ -52,39 +75,33 @@ cd /opt/dmrlink
|
||||
# cp $currentdir/talkgroup_ids.csv /opt/dmrlink
|
||||
|
||||
# Copy ipsc directory into each app directory
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/IPSC_Bridge/
|
||||
#cp -rf $currentdir/ipsc/ /opt/dmrlink/bridge/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/confbridge/
|
||||
#cp -rf $currentdir/ipsc/ /opt/dmrlink/log/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/playback/
|
||||
#cp -rf $currentdir/ipsc/ /opt/dmrlink/play_group/
|
||||
cp -rf $currentdir/ipsc/ /opt/dmrlink/proxy/
|
||||
#cp -rf $currentdir/ipsc/ /opt/dmrlink/rcm/
|
||||
#cp -rf $currentdir/ipsc/ /opt/dmrlink/record/
|
||||
cp -rf $currentdir/ipsc/ $PREFIX/confbridge/
|
||||
cp -rf $currentdir/ipsc/ $PREFIX/playback/
|
||||
cp -rf $currentdir/ipsc/ $PREFIX/proxy/
|
||||
|
||||
# Put a copy of the samples together for easy reference
|
||||
#cp $currentdir/bridge_rules_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/samples
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/IPSC_Bridge.cfg /opt/dmrlink/samples
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py $PREFIX/samples
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/samples
|
||||
#cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/samples
|
||||
cp $currentdir/playback_config_SAMPLE.py $PREFIX/samples
|
||||
#cp $currentdir/ambe_audio.cfg /opt/dmrlink/samples
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/samples
|
||||
|
||||
# Put the doc together for easy reference
|
||||
cp -rf $currentdir/documents /opt/dmrlink
|
||||
cp $currentdir/LICENSE.txt /opt/dmrlink/documents
|
||||
cp $currentdir/requirements.txt /opt/dmrlink/documents
|
||||
# cp $currentdir/IPSC_Bridge_commands.txt /opt/dmrlink/documents
|
||||
cp -rf $currentdir/documents $PREFIX
|
||||
cp $currentdir/LICENSE.txt $PREFIX/documents
|
||||
cp $currentdir/requirements.txt $PREFIX/documents
|
||||
#cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/documents
|
||||
|
||||
# IPSC_Bridge
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/IPSC_Bridge/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/IPSC_Bridge/
|
||||
# ambe_audio
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/ambe_audio/
|
||||
#
|
||||
cp $currentdir/IPSC_Bridge.cfg /opt/dmrlink/IPSC_Bridge/
|
||||
cp $currentdir/IPSC_Bridge.py /opt/dmrlink/IPSC_Bridge/
|
||||
# cp $currentdir/IPSC_Bridge_commands.txt /opt/dmrlink/IPSC_Bridge/
|
||||
cp $currentdir/template.bin /opt/dmrlink/IPSC_Bridge/
|
||||
#cp $currentdir/ambe_audio.cfg /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/ambe_audio.py /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/ambe_audio_commands.txt /opt/dmrlink/ambe_audio/
|
||||
#cp $currentdir/template.bin /opt/dmrlink/ambe_audio/
|
||||
|
||||
# Bridge app
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/bridge/
|
||||
@ -96,13 +113,13 @@ cp $currentdir/template.bin /opt/dmrlink/IPSC_Bridge/
|
||||
#cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/bridge/
|
||||
|
||||
# ConfBridge app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/confbridge/
|
||||
cp $currentdir/dmrlink.py $PREFIX/confbridge/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/confbridge/
|
||||
#
|
||||
cp $currentdir/confbridge.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/confbridge.py $PREFIX/confbridge/
|
||||
cp $currentdir/confbridge_rules_SAMPLE.py $PREFIX/confbridge/
|
||||
#cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
cp $currentdir/sub_acl_SAMPLE.py $PREFIX/confbridge/
|
||||
|
||||
# Log app
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/log/
|
||||
@ -111,11 +128,11 @@ cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/confbridge/
|
||||
#cp $currentdir/log.py /opt/dmrlink/log/
|
||||
|
||||
# Playback (Parrot)
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/playback/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/playback/
|
||||
cp $currentdir/dmrlink.py $PREFIX/playback/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/playback/
|
||||
#
|
||||
cp $currentdir/playback.py /opt/dmrlink/playback/
|
||||
cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/playback/
|
||||
cp $currentdir/playback.py $PREFIX/playback/
|
||||
cp $currentdir/playback_config_SAMPLE.py $PREFIX/playback/
|
||||
|
||||
# Play Group app
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/play_group/
|
||||
@ -124,12 +141,12 @@ cp $currentdir/playback_config_SAMPLE.py /opt/dmrlink/playback/
|
||||
#cp $currentdir/play_group.py /opt/dmrlink/play_group/
|
||||
|
||||
# proxy app
|
||||
cp $currentdir/dmrlink.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg /opt/dmrlink/proxy/
|
||||
cp $currentdir/dmrlink.py $PREFIX/proxy/
|
||||
cp $currentdir/dmrlink_SAMPLE.cfg $PREFIX/proxy/
|
||||
#
|
||||
cp $currentdir/proxy.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/known_bridges_SAMPLE.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/sub_acl_SAMPLE.py /opt/dmrlink/proxy/
|
||||
cp $currentdir/proxy.py $PREFIX/proxy/
|
||||
#cp $currentdir/known_bridges_SAMPLE.py $PREFIX/proxy/
|
||||
cp $currentdir/sub_acl_SAMPLE.py $PREFIX/proxy/
|
||||
|
||||
# rcm app
|
||||
#cp $currentdir/dmrlink.py /opt/dmrlink/rcm/
|
||||
|
24
proxy.py
24
proxy.py
@ -72,16 +72,28 @@ __email__ = 'n0mjs@me.com'
|
||||
# are not yet implemented.
|
||||
def build_acl(_sub_acl):
|
||||
try:
|
||||
logger.info('ACL file found, importing entries. This will take about 1.5 seconds per 1 million IDs')
|
||||
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')
|
||||
ACL_ACTION = acl_file.ACL_ACTION
|
||||
ACL = acl_file.ACL_ACTION
|
||||
sections = acl_file.ACL.split(':')
|
||||
ACL_ACTION = sections[0]
|
||||
entries_str = sections[1]
|
||||
ACL = set()
|
||||
|
||||
for entry in entries_str.split(','):
|
||||
if '-' in entry:
|
||||
start,end = entry.split('-')
|
||||
start,end = int(start), int(end)
|
||||
for id in range(start, end+1):
|
||||
ACL.add(hex_str_3(id))
|
||||
else:
|
||||
id = int(entry)
|
||||
ACL.add(hex_str_3(id))
|
||||
|
||||
logger.info('ACL loaded: action "{}" for {:,} radio IDs'.format(ACL_ACTION, len(ACL)))
|
||||
|
||||
except ImportError:
|
||||
logger.info('ACL file not found or invalid - all subscriber IDs are valid')
|
||||
ACL_ACTION = 'NONE'
|
||||
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
|
||||
|
0
requirements.txt
Normal file → Executable file
0
requirements.txt
Normal file → Executable file
12
sub_acl_SAMPLE.py
Normal file → Executable file
12
sub_acl_SAMPLE.py
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
ACL_ACTION = "DENY" # May be PERMIT|DENY
|
||||
ACL = [
|
||||
1234001,
|
||||
1234002,
|
||||
1234003
|
||||
]
|
||||
# The 'action' May be PERMIT|DENY
|
||||
# Each entry may be a single radio id, or a hypenated range (e.g. 1-2999)
|
||||
# Format:
|
||||
# ACL = 'action:id|start-end|,id|start-end,....'
|
||||
|
||||
ACL = 'DENY:1-2999,16777215'
|
17
systemd/ambe_audio.service
Executable file
17
systemd/ambe_audio.service
Executable file
@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=DMRlink ambe audio Service
|
||||
# Description=Place this file in /lib/systemd/system
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
StandardOutput=null
|
||||
WorkingDirectory=/opt/dmrlink/ambe_audio
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ExecStart=/usr/bin/python /opt/dmrlink/ambe_audio/ambe_audio.py
|
||||
ExecReload=/bin/kill -2 $MAINPID
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=network-online.target
|
||||
|
0
systemd/bridge.service
Normal file → Executable file
0
systemd/bridge.service
Normal file → Executable file
@ -1,17 +0,0 @@
|
||||
[Unit]
|
||||
Description=DMRlink IPSC_Bridge Service
|
||||
# Place this file in /lib/systemd/system
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
StandardOutput=null
|
||||
WorkingDirectory=/opt/dmrlink/IPSC_Bridge
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
ExecStart=/usr/bin/python /opt/dmrlink/IPSC_Bridge/IPSC_Bridge.py
|
||||
ExecReload=/bin/kill -2 $MAINPID
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=network-online.target
|
||||
|
0
systemd/playback.service
Normal file → Executable file
0
systemd/playback.service
Normal file → Executable file
0
template.bin
Normal file → Executable file
0
template.bin
Normal file → Executable file
Loading…
Reference in New Issue
Block a user