2017-06-18 20:46:50 -04:00
#!/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
2017-06-20 14:01:49 -04:00
from dmr_utils import ambe_utils
from dmr_utils . ambe_bridge import AMBE_HB
2017-06-18 20:46:50 -04:00
# 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 '
2017-06-20 14:29:33 -04:00
__version__ = ' 20170620 '
2017-06-18 20:46:50 -04:00
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 ( ' HB_Bridge.cfg ' )
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 ( ) )
# 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
parser = argparse . ArgumentParser ( )
parser . add_argument ( ' -c ' , ' --config ' , action = ' store ' , dest = ' CONFIG_FILE ' , help = ' /full/path/to/config.file (usually hblink.cfg) ' )
parser . add_argument ( ' -l ' , ' --logging ' , action = ' store ' , dest = ' LOG_LEVEL ' , help = ' Override config file logging level. ' )
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 '
# 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 ( )