HBLink/HB_Bridge.py

248 lines
11 KiB
Python

#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2017 Mike Zingman N4IRR
#
# 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
###############################################################################
'''
'''
from __future__ import print_function
# Python modules we need
import sys
from bitarray import bitarray
from bitstring import BitArray
from bitstring import BitString
import struct
from time import time, sleep
from importlib import import_module
from binascii import b2a_hex as ahex
from random import randint
import sys, socket, ConfigParser, thread, traceback
from threading import Lock
from time import time, sleep, clock, localtime, strftime
# Twisted is pretty important, so I keep it separate
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from twisted.internet import task
# Things we import from the main hblink module
from hblink import HBSYSTEM, systems, int_id, hblink_handler
from dmr_utils.utils import hex_str_3, hex_str_4, int_id, get_alias
from dmr_utils import decode, bptc, const, golay, qr
import hb_config
import hb_log
import hb_const
from dmr_utils import ambe_utils
from dmr_utils.ambe_bridge import AMBE_HB
# Does anybody read this stuff? There's a PEP somewhere that says I should do this.
__author__ = 'Mike Zingman, N4IRR and Cortney T. Buffington, N0MJS'
__copyright__ = 'Copyright (c) 2017 Mike Zingman N4IRR'
__credits__ = 'Cortney T. Buffington, N0MJS; Colin Durbridge, G4EML, Steve Zingman, N4IRS; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT'
__license__ = 'GNU GPLv3'
__maintainer__ = 'Cort Buffington, N0MJS'
__email__ = 'n0mjs@me.com'
__status__ = 'pre-alpha'
__version__ = '20170620'
mutex = Lock() # Used to synchronize Peer I/O in different threads
class TRANSLATE:
def __init__(self, config_file):
self.translate = {}
self.load_config(config_file)
pass
def add_rule( self, tg, export_rule):
self.translate[str(tg)] = export_rule
#print(int_id(tg), export_rule)
def delete_rule(self, tg):
if str(tg) in self.translate:
del self.translate[str(tg)]
def find_rule(self, tg, slot):
if str(tg) in self.translate:
return self.translate[str(tg)]
return (tg, slot)
def load_config(self, config_file):
print('load config file', config_file)
pass
# translation structure. IMPORT_TO translates foreign (TG,TS) to local. EXPORT_AS translates local (TG,TS) to foreign values
translate = TRANSLATE('config.file')
class HB_BRIDGE(HBSYSTEM):
def __init__(self, _name, _config, _logger):
HBSYSTEM.__init__(self, _name, _config, _logger)
self._ambeRxPort = 31003 # Port to listen on for AMBE frames to transmit to all peers
self._gateway = "127.0.0.1" # IP address of Analog_Bridge app
self._gateway_port = 31000 # Port Analog_Bridge is listening on for AMBE frames to decode
self.load_configuration(cli_args.BRIDGE_CONFIG_FILE)
self.hb_ambe = AMBE_HB(self, _name, _config, _logger, self._ambeRxPort)
self._sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
def get_globals(self):
return (subscriber_ids, talkgroup_ids, peer_ids)
def get_repeater_id(self, import_id):
if self._config['MODE'] == 'CLIENT': # only clients have radio_id defined, masters do not
return self._config['RADIO_ID']
return import_id
# Load configuration from file
def load_configuration( self, _file_name ):
config = ConfigParser.ConfigParser()
if not config.read(_file_name):
sys.exit('Configuration file \''+_file_name+'\' is not a valid configuration file! Exiting...')
try:
for section in config.sections():
if section == 'DEFAULTS':
self._ambeRxPort = int(config.get(section, 'fromGatewayPort').split(None)[0]) # Port to listen on for AMBE frames to transmit to all peers
self._gateway = config.get(section, 'gateway').split(None)[0] # IP address of Analog_Bridge app
self._gateway_port = int(config.get(section, 'toGatewayPort').split(None)[0]) # Port Analog_Bridge is listening on for AMBE frames to decode
if section == 'RULES':
for rule in config.items(section):
_old_tg, _new_tg, _new_slot = rule[1].split(',')
translate.add_rule(hex_str_3(int(_old_tg)), (hex_str_3(int(_new_tg)), int(_new_slot)))
except ConfigParser.Error, err:
traceback.print_exc()
sys.exit('Could not parse configuration file, ' + _file_name + ', exiting...')
# HBLink callback with DMR data from perr/master. Send this data to any partner listening
def dmrd_received(self, _radio_id, _rf_src, _dst_id, _seq, _slot, _call_type, _frame_type, _dtype_vseq, _stream_id, _data):
_dst_id, _slot = translate.find_rule(_dst_id,_slot)
_tx_slot = self.hb_ambe.tx[_slot]
_seq = ord(_data[4])
_tx_slot.frame_count += 1
if (_stream_id != _tx_slot.stream_id):
self.hb_ambe.begin_call(_slot, _rf_src, _dst_id, _radio_id, _tx_slot.cc, _seq, _stream_id)
_tx_slot.lastSeq = _seq
if (_frame_type == hb_const.HBPF_DATA_SYNC) and (_dtype_vseq == hb_const.HBPF_SLT_VTERM) and (_tx_slot.type != hb_const.HBPF_SLT_VTERM):
self.hb_ambe.end_call(_tx_slot)
if (int_id(_data[15]) & 0x20) == 0:
_dmr_frame = BitArray('0x'+ahex(_data[20:]))
_ambe = _dmr_frame[0:108] + _dmr_frame[156:264]
self.hb_ambe.export_voice(_tx_slot, _seq, _ambe.tobytes())
else:
_tx_slot.lastSeq = _seq
# The methods below are overridden becuse the launchUDP thread can also wite to a master or client async and confuse the master
# A lock is used to synchronize the two threads so that the resource is protected
def send_master(self, _packet):
mutex.acquire()
HBSYSTEM.send_master(self, _packet)
mutex.release()
def send_clients(self, _packet):
mutex.acquire()
HBSYSTEM.send_clients(self, _packet)
mutex.release()
############################################################################################################
# MAIN PROGRAM LOOP STARTS HERE
############################################################################################################
if __name__ == '__main__':
import argparse
import sys
import os
import signal
from dmr_utils.utils import try_download, mk_id_dict
# 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
# Added the capability to define a custom bridge config file, multiple bridges are needed when doing things like Analog_Bridge
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config', action='store', dest='CONFIG_FILE', help='/full/path/to/config.file (default hblink.cfg)')
parser.add_argument('-l', '--logging', action='store', dest='LOG_LEVEL', help='Override config file logging level.')
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.CONFIG_FILE:
cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.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__))+'/HB_Bridge.cfg'
# Call the external routine to build the configuration dictionary
CONFIG = hb_config.build_config(cli_args.CONFIG_FILE)
# Start the system logger
if cli_args.LOG_LEVEL:
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
logger = hb_log.config_logging(CONFIG['LOGGER'])
logger.debug('Logging system started, anything from here on gets logged')
# Set up the signal handler
def sig_handler(_signal, _frame):
logger.info('SHUTDOWN: HB_Bridge IS TERMINATING WITH SIGNAL %s', str(_signal))
hblink_handler(_signal, _frame, logger)
logger.info('SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
reactor.stop()
# Set signal handers so that we can gracefully exit if need be
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, sig_handler)
# 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')
# HBlink instance creation
logger.info('HBlink \'HB_Bridge.py\' (c) 2017 Mike Zingman N4IRR, N0MJS - SYSTEM STARTING...')
logger.info('Version %s', __version__)
for system in CONFIG['SYSTEMS']:
if CONFIG['SYSTEMS'][system]['ENABLED']:
systems[system] = HB_BRIDGE(system, CONFIG, logger)
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
logger.debug('%s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
reactor.run()