From 7280f835d7f557c1dfeca082e584d713867d7e3c Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 27 Dec 2021 14:10:17 +0000 Subject: [PATCH] more tidying up --- FreeDMR/Protocol/HomeBrewProtocol.py | 4 +- FreeDMR/Protocol/OpenBridge.py | 4 +- FreeDMR/Utilities/mysql_config.py | 107 ------- FreeDMR/Utilities/read_ambe.py | 175 ------------ FreeDMR/freedmr.py | 4 +- bridge.py | 4 +- bridge_master.py | 12 +- config.py | 408 --------------------------- languages.py | 1 - log.py | 93 ------ mk_voice.py | 107 ------- playback.py | 8 +- 12 files changed, 18 insertions(+), 909 deletions(-) delete mode 100644 FreeDMR/Utilities/mysql_config.py delete mode 100644 FreeDMR/Utilities/read_ambe.py delete mode 100755 config.py delete mode 100644 languages.py delete mode 100755 log.py delete mode 100755 mk_voice.py diff --git a/FreeDMR/Protocol/HomeBrewProtocol.py b/FreeDMR/Protocol/HomeBrewProtocol.py index bef5868..158bb79 100644 --- a/FreeDMR/Protocol/HomeBrewProtocol.py +++ b/FreeDMR/Protocol/HomeBrewProtocol.py @@ -42,8 +42,8 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Other files we pull from -- this is mostly for readability and segmentation -import log -import config +import FreeDMR.Utilities.log as log +import FreeDMR.Config.config as config from FreeDMR.Const.const import * from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict diff --git a/FreeDMR/Protocol/OpenBridge.py b/FreeDMR/Protocol/OpenBridge.py index 4699db4..b4b7cf4 100644 --- a/FreeDMR/Protocol/OpenBridge.py +++ b/FreeDMR/Protocol/OpenBridge.py @@ -42,8 +42,8 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Other files we pull from -- this is mostly for readability and segmentation -import log -import config +import FreeDMR.Utilities.log as log +import FreeDMR.Config.config as config from FreeDMR.Const.const import * from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict diff --git a/FreeDMR/Utilities/mysql_config.py b/FreeDMR/Utilities/mysql_config.py deleted file mode 100644 index fe8553b..0000000 --- a/FreeDMR/Utilities/mysql_config.py +++ /dev/null @@ -1,107 +0,0 @@ -import mysql.connector -from mysql.connector import errorcode -#import mysql.connector.pooling - -# Does anybody read this stuff? There's a PEP somewhere that says I should do this. -__author__ = 'Simon Adlem - G7RZU' -__copyright__ = 'Copyright (c) Simon Adlem, G7RZU 2020,2021' -__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT; Jon Lee, G4TSN; Norman Williams, M6NBP' -__license__ = 'GNU GPLv3' -__maintainer__ = 'Simon Adlem G7RZU' -__email__ = 'simon@gb7fr.org.uk' - - -class useMYSQL: - #Init new object - def __init__(self, server,user,password,database,table,logger): - self.server = server - self.user = user - self.password = password - self.database = database - self.table = table - self.logger = logger - - #Connect - def con(self): - logger = self.logger - try: - self.db = mysql.connector.connect( - host=self.server, - user=self.user, - password=self.password, - database=self.database, - # pool_name = "hblink_master", - # pool_size = 2 - ) - except mysql.connector.Error as err: - if err.errno == errorcode.ER_ACCESS_DENIED_ERROR: - logger.info('(MYSQL) username or password error') - return (False) - elif err.errno == errorcode.ER_BAD_DB_ERROR: - logger.info('(MYSQL) DB Error') - return (False) - else: - logger.info('(MYSQL) error: %s',err) - return(False) - - return(True) - - #Close DB connection - def close(self): - self.db.close() - - #Get config from DB - def getConfig(self): - - CONFIG = {} - CONFIG['SYSTEMS'] = {} - - _cursor = self.db.cursor() - - try: - _cursor.execute("select * from {} where MODE='MASTER'".format(self.table)) - except mysql.connector.Error as err: - _cursor.close() - logger.info('(MYSQL) error, problem with cursor execute') - raise Exception('Problem with cursor execute') - - for (callsign, mode, enabled, _repeat, max_peers, export_ambe, ip, port, passphrase, group_hangtime, use_acl, reg_acl, sub_acl, tgid_ts1_acl, tgid_ts2_acl, default_ua_timer, single_mode, voice_ident,ts1_static,ts2_static,default_reflector, announce_lang) in _cursor: - try: - CONFIG['SYSTEMS'].update({callsign: { - 'MODE': mode, - 'ENABLED': bool(enabled), - 'REPEAT': bool(_repeat), - 'MAX_PEERS': int(max_peers), - 'IP': ip, - 'PORT': int(port), - 'PASSPHRASE': bytes(passphrase, 'utf-8'), - 'GROUP_HANGTIME': int(group_hangtime), - 'USE_ACL': bool(use_acl), - 'REG_ACL': reg_acl, - 'SUB_ACL': sub_acl, - 'TG1_ACL': tgid_ts1_acl, - 'TG2_ACL': tgid_ts2_acl, - 'DEFAULT_UA_TIMER': int(default_ua_timer), - 'SINGLE_MODE': bool(single_mode), - 'VOICE_IDENT': bool(voice_ident), - 'TS1_STATIC': ts1_static, - 'TS2_STATIC': ts2_static, - 'DEFAULT_REFLECTOR': int(default_reflector), - 'GENERATOR': int(1), - 'ANNOUNCEMENT_LANGUAGE': announce_lang - }}) - CONFIG['SYSTEMS'][callsign].update({'PEERS': {}}) - except TypeError: - logger.info('(MYSQL) Problem with data from MySQL - TypeError, carrying on to next row') - - return(CONFIG['SYSTEMS']) - - -#For testing -if __name__ == '__main__': - - sql = useMYSQL("ip","user","pass","db") - - sql.con() - - print( sql.getConfig()) diff --git a/FreeDMR/Utilities/read_ambe.py b/FreeDMR/Utilities/read_ambe.py deleted file mode 100644 index 0442aba..0000000 --- a/FreeDMR/Utilities/read_ambe.py +++ /dev/null @@ -1,175 +0,0 @@ -############################################################################### -# Copyright (C) 2020 Simon Adlem, G7RZU -# -# 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 bitarray import bitarray -from itertools import islice -import os -import glob - -class readAMBE: - - def __init__(self, lang,path): - self.langcsv = lang - self.langs = lang.split(',') - self.path = path - - def _make_bursts(self,data): - it = iter(data) - for i in range(0, len(data), 108): - yield bitarray([k for k in islice(it, 108)] ) - - #Read indexed files - def readfiles(self): - - _AMBE_LENGTH = 9 - - _wordBADictofDicts = {} - - for _lang in self.langs: - - _prefix = self.path+_lang - _wordBADict = {} - - indexDict = {} - - if os.path.isdir(_prefix): - ambeBytearray = {} - _wordBitarray = bitarray(endian='big') - _wordBADict = {} - _glob = _prefix + "/*.ambe" - for ambe in glob.glob(_glob): - basename = os.path.basename(ambe) - _voice,ext = basename.split('.') - inambe = open(ambe,'rb') - _wordBitarray.frombytes(inambe.read()) - inambe.close() - _wordBADict[_voice] = [] - pairs = 1 - _lastburst = '' - for _burst in self._make_bursts(_wordBitarray): - #Not sure if we need to pad or not? Seems to make little difference. - if len(_burst) < 108: - pad = (108 - len(_burst)) - for i in range(0,pad,1): - _burst.append(False) - if pairs == 2: - _wordBADict[_voice].append([_lastburst,_burst]) - _lastburst = '' - pairs = 1 - next - else: - pairs = pairs + 1 - _lastburst = _burst - - _wordBitarray.clear() - _wordBADict['silence'] = ([ - [bitarray('101011000000101010100000010000000000001000000000000000000000010001000000010000000000100000000000100000000000'), - bitarray('001010110000001010101000000100000000000010000000000000000000000100010000000100000000001000000000001000000000')] - ]) - _wordBADictofDicts[_lang] = _wordBADict - else: - try: - with open(_prefix+'.indx') as index: - for line in index: - (voice,start,length) = line.split() - indexDict[voice] = [int(start) * _AMBE_LENGTH ,int(length) * _AMBE_LENGTH] - index.close() - except IOError: - return False - - ambeBytearray = {} - _wordBitarray = bitarray(endian='big') - _wordBADict = {} - try: - with open(_prefix+'.ambe','rb') as ambe: - for _voice in indexDict: - ambe.seek(indexDict[_voice][0]) - _wordBitarray.frombytes(ambe.read(indexDict[_voice][1])) - #108 - _wordBADict[_voice] = [] - pairs = 1 - _lastburst = '' - for _burst in self._make_bursts(_wordBitarray): - #Not sure if we need to pad or not? Seems to make little difference. - if len(_burst) < 108: - pad = (108 - len(_burst)) - for i in range(0,pad,1): - _burst.append(False) - if pairs == 2: - _wordBADict[_voice].append([_lastburst,_burst]) - _lastburst = '' - pairs = 1 - next - else: - pairs = pairs + 1 - _lastburst = _burst - - _wordBitarray.clear() - ambe.close() - except IOError: - return False - _wordBADict['silence'] = ([ - [bitarray('101011000000101010100000010000000000001000000000000000000000010001000000010000000000100000000000100000000000'), - bitarray('001010110000001010101000000100000000000010000000000000000000000100010000000100000000001000000000001000000000')] - ]) - _wordBADictofDicts[_lang] = _wordBADict - - return _wordBADictofDicts - - #Read a single ambe file from the audio directory - def readSingleFile(self,filename): - ambeBytearray = {} - _wordBitarray = bitarray(endian='big') - _wordBA= [] - try: - with open(self.path+filename,'rb') as ambe: - _wordBitarray.frombytes(ambe.read()) - #108 - _wordBA = [] - pairs = 1 - _lastburst = '' - for _burst in self._make_bursts(_wordBitarray): -#Not sure if we need to pad or not? Seems to make little difference. - if len(_burst) < 108: - pad = (108 - len(_burst)) - for i in range(0,pad,1): - _burst.append(False) - if pairs == 2: - _wordBA.append([_lastburst,_burst]) - _lastburst = '' - pairs = 1 - next - else: - pairs = pairs + 1 - _lastburst = _burst - - _wordBitarray.clear() - ambe.close() - except IOError: - raise - - return(_wordBA) - - -if __name__ == '__main__': - - #test = readAMBE('en_GB','./Audio/') - - #print(test.readfiles()) - test = readAMBE('en_GB_2','./Audio/') - print(test.readfiles()) - print(test.readSingleFile('44xx.ambe')) diff --git a/FreeDMR/freedmr.py b/FreeDMR/freedmr.py index 919e74a..bfab9e7 100644 --- a/FreeDMR/freedmr.py +++ b/FreeDMR/freedmr.py @@ -42,8 +42,8 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Other files we pull from -- this is mostly for readability and segmentation -import log -import config +import FreeDMR.Utilities.log as log +import FreeDMR.Config.config as config from FreeDMR.Const.const import * from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict diff --git a/bridge.py b/bridge.py index 1a66388..84c275b 100755 --- a/bridge.py +++ b/bridge.py @@ -45,8 +45,8 @@ from twisted.internet import reactor, task from FreeDMR.freedmr import HBSYSTEM, OPENBRIDGE, systems, freedmr_handler, reportFactory, REPORT_OPCODES, mk_aliases from dmr_utils3.utils import bytes_3, int_id, get_alias from dmr_utils3 import decode, bptc, const -import config -import log +import FreeDMR.Config.config as config +import FreeDMR.Utilities.log as log from FreeDMR.Const.const import * # Stuff for socket reporting diff --git a/bridge_master.py b/bridge_master.py index 211ff68..a04f0e3 100644 --- a/bridge_master.py +++ b/bridge_master.py @@ -49,21 +49,21 @@ from twisted.internet import reactor, task from FreeDMR.freedmr import HBSYSTEM, OPENBRIDGE, systems, freedmr_handler, reportFactory, REPORT_OPCODES, mk_aliases, acl_check from dmr_utils3.utils import bytes_3, int_id, get_alias, bytes_4 from dmr_utils3 import decode, bptc, const -import config -from config import acl_build -import log +import FreeDMR.Config.config as config +from FreeDMR.Config.config import acl_build +import FreeDMR.Utilities.log as log from FreeDMR.Const.const import * -from mk_voice import pkt_gen +from FreeDMR.AMBE.mk_voice import pkt_gen #from voice_lib import words #Read voices -from FreeDMR.Utilities.read_ambe import readAMBE +from FreeDMR.AMBE.read_ambe import readAMBE #Remap some words for certain languages from FreeDMR.i8n.i8n_voice_map import voiceMap #MySQL -from FreeDMR.Utilities.mysql_config import useMYSQL +from FreeDMR.Config.mysql_config import useMYSQL # Stuff for socket reporting import pickle diff --git a/config.py b/config.py deleted file mode 100755 index 81964f3..0000000 --- a/config.py +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/env python -# -############################################################################### -# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS -# -# 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 module generates the configuration data structure for hblink.py and -assoicated programs that use it. It has been seaparated into a different -module so as to keep hblink.py easeier to navigate. This file only needs -updated if the items in the main configuraiton file (usually hblink.cfg) -change. -''' - -import configparser -import sys -import FreeDMR.Const.const as const - -import socket -import ipaddress -from socket import gethostbyname -from languages import languages - - -# Does anybody read this stuff? There's a PEP somewhere that says I should do this. -__author__ = 'Cortney T. Buffington, N0MJS' -__copyright__ = '(c) Simon Adlem, G7RZU 2020-2021, Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' -__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' -__license__ = 'GNU GPLv3' -__maintainer__ = 'Simon Adlem, G7RZU' -__email__ = 'simon@gb7fr.org.uk' - -# Processing of ALS goes here. It's separated from the acl_build function because this -# code is hblink config-file format specific, and acl_build is abstracted -def process_acls(_config): - # Global registration ACL - _config['GLOBAL']['REG_ACL'] = acl_build(_config['GLOBAL']['REG_ACL'], const.PEER_MAX) - - # Global subscriber and TGID ACLs - for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: - _config['GLOBAL'][acl] = acl_build(_config['GLOBAL'][acl], const.ID_MAX) - - # System level ACLs - for system in _config['SYSTEMS']: - # Registration ACLs (which make no sense for peer systems) - if _config['SYSTEMS'][system]['MODE'] == 'MASTER': - _config['SYSTEMS'][system]['REG_ACL'] = acl_build(_config['SYSTEMS'][system]['REG_ACL'], const.PEER_MAX) - - # Subscriber and TGID ACLs (valid for all system types) - for acl in ['SUB_ACL', 'TG1_ACL', 'TG2_ACL']: - _config['SYSTEMS'][system][acl] = acl_build(_config['SYSTEMS'][system][acl], const.ID_MAX) - -# Create an access control list that is programatically useable from human readable: -# ORIGINAL: 'DENY:1-5,3120101,3120124' -# PROCESSED: (False, set([(1, 5), (3120124, 3120124), (3120101, 3120101)])) -def acl_build(_acl, _max): - if not _acl: - return(True, set((const.ID_MIN, _max))) - - acl = [] #set() - sections = _acl.split(':') - - if sections[0] == 'PERMIT': - action = True - else: - action = False - - for entry in sections[1].split(','): - if entry == 'ALL': - acl.append((const.ID_MIN, _max)) - break - - elif '-' in entry: - start,end = entry.split('-') - start,end = int(start), int(end) - if (const.ID_MIN <= start <= _max) or (const.ID_MIN <= end <= _max): - acl.append((start, end)) - else: - sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {})IN RANGE-BASED ENTRY: {}'.format(const.ID_MIN, _max, entry)) - else: - id = int(entry) - if (const.ID_MIN <= id <= _max): - acl.append((id, id)) - else: - sys.exit('ACL CREATION ERROR, VALUE OUT OF RANGE ({} - {}) IN SINGLE ID ENTRY: {}'.format(const.ID_MIN, _max, entry)) - - return (action, acl) - -def IsIPv4Address(ip): - try: - ipaddress.IPv4Address(ip) - return True - except ValueError as errorCode: - pass - return False - -def IsIPv6Address(ip): - try: - ipaddress.IPv6Address(ip) - return True - except ValueError as errorCode: - pass - return False - -def build_config(_config_file): - config = configparser.ConfigParser() - - if not config.read(_config_file): - sys.exit('Configuration file \''+_config_file+'\' is not a valid configuration file! Exiting...') - - CONFIG = {} - CONFIG['GLOBAL'] = {} - CONFIG['REPORTS'] = {} - CONFIG['LOGGER'] = {} - CONFIG['ALIASES'] = {} - CONFIG['SYSTEMS'] = {} - CONFIG['MYSQL'] = {} - - try: - for section in config.sections(): - if section == 'GLOBAL': - CONFIG['GLOBAL'].update({ - 'PATH': config.get(section, 'PATH'), - 'PING_TIME': config.getint(section, 'PING_TIME'), - 'MAX_MISSED': config.getint(section, 'MAX_MISSED'), - 'USE_ACL': config.get(section, 'USE_ACL'), - 'REG_ACL': config.get(section, 'REG_ACL'), - 'SUB_ACL': config.get(section, 'SUB_ACL'), - 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), - 'TG2_ACL': config.get(section, 'TGID_TS2_ACL'), - 'GEN_STAT_BRIDGES': config.getboolean(section, 'GEN_STAT_BRIDGES'), - 'ALLOW_NULL_PASSPHRASE': config.getboolean(section, 'ALLOW_NULL_PASSPHRASE'), - 'ANNOUNCEMENT_LANGUAGES': config.get(section, 'ANNOUNCEMENT_LANGUAGES'), - 'SERVER_ID': config.getint(section, 'SERVER_ID').to_bytes(4, 'big') - - }) - if not CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES']: - CONFIG['GLOBAL']['ANNOUNCEMENT_LANGUAGES'] = languages - - elif section == 'REPORTS': - CONFIG['REPORTS'].update({ - 'REPORT': config.getboolean(section, 'REPORT'), - 'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'), - 'REPORT_PORT': config.getint(section, 'REPORT_PORT'), - 'REPORT_CLIENTS': config.get(section, 'REPORT_CLIENTS').split(',') - }) - - elif section == 'LOGGER': - CONFIG['LOGGER'].update({ - 'LOG_FILE': config.get(section, 'LOG_FILE'), - 'LOG_HANDLERS': config.get(section, 'LOG_HANDLERS'), - 'LOG_LEVEL': config.get(section, 'LOG_LEVEL'), - 'LOG_NAME': config.get(section, 'LOG_NAME') - }) - if not CONFIG['LOGGER']['LOG_FILE']: - CONFIG['LOGGER']['LOG_FILE'] = '/dev/null' - - elif section == 'ALIASES': - CONFIG['ALIASES'].update({ - 'TRY_DOWNLOAD': config.getboolean(section, 'TRY_DOWNLOAD'), - 'PATH': config.get(section, 'PATH'), - 'PEER_FILE': config.get(section, 'PEER_FILE'), - 'SUBSCRIBER_FILE': config.get(section, 'SUBSCRIBER_FILE'), - 'TGID_FILE': config.get(section, 'TGID_FILE'), - 'PEER_URL': config.get(section, 'PEER_URL'), - 'SUBSCRIBER_URL': config.get(section, 'SUBSCRIBER_URL'), - 'TGID_URL': config.get(section, 'TGID_URL'), - 'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400, - 'SUB_MAP_FILE': config.get(section, 'SUB_MAP_FILE') - }) - - elif section == 'MYSQL': - CONFIG['MYSQL'].update({ - 'USE_MYSQL': config.getboolean(section, 'USE_MYSQL'), - 'USER': config.get(section, 'USER'), - 'PASS': config.get(section, 'PASS'), - 'DB': config.get(section, 'DB'), - 'SERVER': config.get(section, 'SERVER'), - 'PORT': config.getint(section,'PORT'), - 'TABLE': config.get(section, 'TABLE') - }) - - - elif config.getboolean(section, 'ENABLED'): - if config.get(section, 'MODE') == 'PEER': - CONFIG['SYSTEMS'].update({section: { - 'MODE': config.get(section, 'MODE'), - 'ENABLED': config.getboolean(section, 'ENABLED'), - 'LOOSE': config.getboolean(section, 'LOOSE'), - 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), - 'IP': gethostbyname(config.get(section, 'IP')), - 'PORT': config.getint(section, 'PORT'), - 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), - 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), - '_MASTER_IP': config.get(section, 'MASTER_IP'), - 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), - 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), - 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), - 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), - 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), - 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), - 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), - 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), - 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), - 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), - 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), - 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), - 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), - 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), - 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), - 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), - 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), - 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), - 'OPTIONS': bytes(config.get(section, 'OPTIONS'), 'utf-8'), - 'USE_ACL': config.getboolean(section, 'USE_ACL'), - 'SUB_ACL': config.get(section, 'SUB_ACL'), - 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), - 'TG2_ACL': config.get(section, 'TGID_TS2_ACL'), - 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE') - }}) - CONFIG['SYSTEMS'][section].update({'STATS': { - 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES - 'CONNECTED': None, - 'PINGS_SENT': 0, - 'PINGS_ACKD': 0, - 'NUM_OUTSTANDING': 0, - 'PING_OUTSTANDING': False, - 'LAST_PING_TX_TIME': 0, - 'LAST_PING_ACK_TIME': 0, - }}) - - if config.get(section, 'MODE') == 'XLXPEER': - CONFIG['SYSTEMS'].update({section: { - 'MODE': config.get(section, 'MODE'), - 'ENABLED': config.getboolean(section, 'ENABLED'), - 'LOOSE': config.getboolean(section, 'LOOSE'), - 'SOCK_ADDR': (gethostbyname(config.get(section, 'IP')), config.getint(section, 'PORT')), - 'IP': gethostbyname(config.get(section, 'IP')), - 'PORT': config.getint(section, 'PORT'), - 'MASTER_SOCKADDR': (gethostbyname(config.get(section, 'MASTER_IP')), config.getint(section, 'MASTER_PORT')), - 'MASTER_IP': gethostbyname(config.get(section, 'MASTER_IP')), - '_MASTER_IP': config.get(section, 'MASTER_IP'), - 'MASTER_PORT': config.getint(section, 'MASTER_PORT'), - 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), - 'CALLSIGN': bytes(config.get(section, 'CALLSIGN').ljust(8)[:8], 'utf-8'), - 'RADIO_ID': config.getint(section, 'RADIO_ID').to_bytes(4, 'big'), - 'RX_FREQ': bytes(config.get(section, 'RX_FREQ').ljust(9)[:9], 'utf-8'), - 'TX_FREQ': bytes(config.get(section, 'TX_FREQ').ljust(9)[:9], 'utf-8'), - 'TX_POWER': bytes(config.get(section, 'TX_POWER').rjust(2,'0'), 'utf-8'), - 'COLORCODE': bytes(config.get(section, 'COLORCODE').rjust(2,'0'), 'utf-8'), - 'LATITUDE': bytes(config.get(section, 'LATITUDE').ljust(8)[:8], 'utf-8'), - 'LONGITUDE': bytes(config.get(section, 'LONGITUDE').ljust(9)[:9], 'utf-8'), - 'HEIGHT': bytes(config.get(section, 'HEIGHT').rjust(3,'0'), 'utf-8'), - 'LOCATION': bytes(config.get(section, 'LOCATION').ljust(20)[:20], 'utf-8'), - 'DESCRIPTION': bytes(config.get(section, 'DESCRIPTION').ljust(19)[:19], 'utf-8'), - 'SLOTS': bytes(config.get(section, 'SLOTS'), 'utf-8'), - 'URL': bytes(config.get(section, 'URL').ljust(124)[:124], 'utf-8'), - 'SOFTWARE_ID': bytes(config.get(section, 'SOFTWARE_ID').ljust(40)[:40], 'utf-8'), - 'PACKAGE_ID': bytes(config.get(section, 'PACKAGE_ID').ljust(40)[:40], 'utf-8'), - 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), - 'XLXMODULE': config.getint(section, 'XLXMODULE'), - 'OPTIONS': '', - 'USE_ACL': config.getboolean(section, 'USE_ACL'), - 'SUB_ACL': config.get(section, 'SUB_ACL'), - 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), - 'TG2_ACL': config.get(section, 'TGID_TS2_ACL'), - 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE') - }}) - CONFIG['SYSTEMS'][section].update({'XLXSTATS': { - 'CONNECTION': 'NO', # NO, RTPL_SENT, AUTHENTICATED, CONFIG-SENT, YES - 'CONNECTED': None, - 'PINGS_SENT': 0, - 'PINGS_ACKD': 0, - 'NUM_OUTSTANDING': 0, - 'PING_OUTSTANDING': False, - 'LAST_PING_TX_TIME': 0, - 'LAST_PING_ACK_TIME': 0, - }}) - - elif config.get(section, 'MODE') == 'MASTER': - CONFIG['SYSTEMS'].update({section: { - 'MODE': config.get(section, 'MODE'), - 'ENABLED': config.getboolean(section, 'ENABLED'), - 'REPEAT': config.getboolean(section, 'REPEAT'), - 'MAX_PEERS': config.getint(section, 'MAX_PEERS'), - 'IP': config.get(section, 'IP'), - 'PORT': config.getint(section, 'PORT'), - 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE'), 'utf-8'), - 'GROUP_HANGTIME': config.getint(section, 'GROUP_HANGTIME'), - 'USE_ACL': config.getboolean(section, 'USE_ACL'), - 'REG_ACL': config.get(section, 'REG_ACL'), - 'SUB_ACL': config.get(section, 'SUB_ACL'), - 'TG1_ACL': config.get(section, 'TGID_TS1_ACL'), - 'TG2_ACL': config.get(section, 'TGID_TS2_ACL'), - 'DEFAULT_UA_TIMER': config.getint(section, 'DEFAULT_UA_TIMER'), - 'SINGLE_MODE': config.getboolean(section, 'SINGLE_MODE'), - 'VOICE_IDENT': config.getboolean(section, 'VOICE_IDENT'), - 'TS1_STATIC': config.get(section,'TS1_STATIC'), - 'TS2_STATIC': config.get(section,'TS2_STATIC'), - 'DEFAULT_REFLECTOR': config.getint(section, 'DEFAULT_REFLECTOR'), - 'GENERATOR': config.getint(section, 'GENERATOR'), - 'ANNOUNCEMENT_LANGUAGE': config.get(section, 'ANNOUNCEMENT_LANGUAGE') - }}) - CONFIG['SYSTEMS'][section].update({'PEERS': {}}) - - elif config.get(section, 'MODE') == 'OPENBRIDGE': - CONFIG['SYSTEMS'].update({section: { - 'MODE': config.get(section, 'MODE'), - 'ENABLED': config.getboolean(section, 'ENABLED'), - 'NETWORK_ID': config.getint(section, 'NETWORK_ID').to_bytes(4, 'big'), - #'OVERRIDE_SERVER_ID': config.getint(section, 'OVERRIDE_SERVER_ID').to_bytes(4, 'big'), - 'IP': config.get(section, 'IP'), - 'PORT': config.getint(section, 'PORT'), - 'PASSPHRASE': bytes(config.get(section, 'PASSPHRASE').ljust(20,'\x00')[:20], 'utf-8'), - #'TARGET_SOCK': (gethostbyname(config.get(section, 'TARGET_IP')), config.getint(section, 'TARGET_PORT')), - 'TARGET_IP': config.get(section, 'TARGET_IP'), - 'TARGET_PORT': config.getint(section, 'TARGET_PORT'), - 'USE_ACL': config.getboolean(section, 'USE_ACL'), - 'SUB_ACL': config.get(section, 'SUB_ACL'), - 'TG1_ACL': config.get(section, 'TGID_ACL'), - 'TG2_ACL': 'PERMIT:ALL', - 'RELAX_CHECKS': config.getboolean(section, 'RELAX_CHECKS'), - 'ENHANCED_OBP': config.getboolean(section, 'ENHANCED_OBP') - }}) - - try: - - if CONFIG['SYSTEMS'][section]['IP'] == '::': - try: - addr_info = socket.getaddrinfo(CONFIG['SYSTEMS'][section]['TARGET_IP'],CONFIG['SYSTEMS'][section]['TARGET_PORT'],socket.AF_INET6, socket.IPPROTO_IP) - except gaierror: - addr_info = socket.getaddrinfo(CONFIG['SYSTEMS'][section]['TARGET_IP'],CONFIG['SYSTEMS'][section]['TARGET_PORT'],socket.AF_INET, socket.IPPROTO_IP) - - elif CONFIG['SYSTEMS'][section]['IP'] and IsIPv6Address(CONFIG['SYSTEMS'][section]['IP']): - addr_info = socket.getaddrinfo(CONFIG['SYSTEMS'][section]['TARGET_IP'],CONFIG['SYSTEMS'][section]['TARGET_PORT'],socket.AF_INET6, socket.IPPROTO_IP) - - elif not CONFIG['SYSTEMS'][section]['IP'] or IsIPv4Address(CONFIG['SYSTEMS'][section]['IP']): - addr_info = socket.getaddrinfo(CONFIG['SYSTEMS'][section]['TARGET_IP'],CONFIG['SYSTEMS'][section]['TARGET_PORT'],socket.AF_INET, socket.IPPROTO_IP) - else: - raise - - family, socktype, proto, canonname, sockaddr = addr_info[0] - CONFIG['SYSTEMS'][section]['TARGET_IP'] = sockaddr[0] - - if CONFIG['SYSTEMS'][section]['IP'] == '::' and IsIPv4Address(CONFIG['SYSTEMS'][section]['TARGET_IP']): - CONFIG['SYSTEMS'][section]['TARGET_IP'] = '::ffff:' + CONFIG['SYSTEMS'][section]['TARGET_IP'] - - CONFIG['SYSTEMS'][section]['TARGET_SOCK'] = (CONFIG['SYSTEMS'][section]['TARGET_IP'],CONFIG['SYSTEMS'][section]['TARGET_PORT']) - - except: - CONFIG['SYSTEMS'][section]['TARGET_IP'] = False - CONFIG['SYSTEMS'][section]['TARGET_SOCK'] = (CONFIG['SYSTEMS'][section]['TARGET_IP'], CONFIG['SYSTEMS'][section]['TARGET_PORT']) - - - except configparser.Error as err: - sys.exit('Error processing configuration file -- {}'.format(err)) - - process_acls(CONFIG) - - return CONFIG - -# Used to run this file direclty and print the config, -# which might be useful for debugging -if __name__ == '__main__': - import sys - import os - import argparse - from pprint import pprint - from dmr_utils3.utils import int_id - - # 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)') - cli_args = parser.parse_args() - - - # Ensure we have a path for the config file, if one wasn't specified, then use the execution directory - if not cli_args.CONFIG_FILE: - cli_args.CONFIG_FILE = os.path.dirname(os.path.abspath(__file__))+'/hblink.cfg' - - CONFIG = build_config(cli_args.CONFIG_FILE) - pprint(CONFIG) - - def acl_check(_id, _acl): - id = int_id(_id) - for entry in _acl[1]: - if entry[0] <= id <= entry[1]: - return _acl[0] - return not _acl[0] - - print(acl_check(b'\x00\x01\x37', CONFIG['GLOBAL']['TG1_ACL'])) diff --git a/languages.py b/languages.py deleted file mode 100644 index a1dbcd8..0000000 --- a/languages.py +++ /dev/null @@ -1 +0,0 @@ -languages = 'en_GB,en_US,es_ES,fr_FR,de_DE,dk_DK,it_IT,no_NO,pl_PL,se_SE,pt_PT,cy_GB,el_GR,th_TH,CW' diff --git a/log.py b/log.py deleted file mode 100755 index 96d5c21..0000000 --- a/log.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python -# -############################################################################### -# Copyright (C) 2016-2018 Cortney T. Buffington, N0MJS -# -# 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 the logging configuration for hblink.py. It changes very infrequently, -so keeping in a separate module keeps hblink.py more concise. this file is -likely to never change. -''' - -import logging -from logging.config import dictConfig - -# Does anybody read this stuff? There's a PEP somewhere that says I should do this. -__author__ = 'Cortney T. Buffington, N0MJS' -__copyright__ = 'Copyright (c) 2016-2018 Cortney T. Buffington, N0MJS and the K0USY Group' -__credits__ = 'Colin Durbridge, G4EML, Steve Zingman, N4IRS; Mike Zingman, N4IRR; Jonathan Naylor, G4KLX; Hans Barthen, DL5DI; Torsten Shultze, DG1HT' -__license__ = 'GNU GPLv3' -__maintainer__ = 'Cort Buffington, N0MJS' -__email__ = 'n0mjs@me.com' - - -def config_logging(_logger): - dictConfig({ - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - }, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' - }, - 'timed': { - 'format': '%(levelname)s %(asctime)s %(message)s' - }, - 'simple': { - 'format': '%(levelname)s %(message)s' - }, - 'syslog': { - 'format': '%(name)s (%(process)d): %(levelname)s %(message)s' - } - }, - 'handlers': { - 'null': { - 'class': 'logging.NullHandler' - }, - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'simple' - }, - 'console-timed': { - 'class': 'logging.StreamHandler', - 'formatter': 'timed' - }, - 'file': { - 'class': 'logging.FileHandler', - 'formatter': 'simple', - 'filename': _logger['LOG_FILE'], - }, - 'file-timed': { - 'class': 'logging.FileHandler', - 'formatter': 'timed', - 'filename': _logger['LOG_FILE'], - }, - 'syslog': { - 'class': 'logging.handlers.SysLogHandler', - 'formatter': 'syslog', - } - }, - 'root': { - 'handlers': _logger['LOG_HANDLERS'].split(','), - 'level': _logger['LOG_LEVEL'], - 'propagate': True, - }, - }) - - return logging.getLogger(_logger['LOG_NAME']) diff --git a/mk_voice.py b/mk_voice.py deleted file mode 100755 index f4f4b27..0000000 --- a/mk_voice.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python -# -############################################################################### -# Copyright (C) 2016-2019 Cortney T. Buffington, N0MJS -# -# 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 bitarray import bitarray -from dmr_utils3 import bptc, golay, qr -from dmr_utils3.utils import bytes_3, bytes_4 -from dmr_utils3.const import EMB, SLOT_TYPE, BS_VOICE_SYNC, BS_DATA_SYNC, LC_OPT -from random import randint -from voice_lib import words - -# Precalculated "dmrbits" (DMRD packet byte 15) -- just (slot << 7 | this value) and you're good to go! -HEADBITS = 0b00100001 -BURSTBITS = [0b00010000,0b00000001,0b00000010,0b00000011,0b00000100,0b00000101] -TERMBITS = 0b00100010 - -# Need a bitstring of 4-bytes of zero for burst F -NULL_EMB_LC = bitarray(endian='big') -NULL_EMB_LC.frombytes(b'\x00\x00\x00\x00') - -# This is where HBP encodes RSSI, it will need to be null -TAIL = b'\x00\x00' - -# WARNING this funciton uses yeild to return a generator that will pass the next HBP packet for a phrase -# each time that it is called. Do NOT try to use it like a normal function. -def pkt_gen(_rf_src, _dst_id, _peer, _slot, _phrase): - - # Calculate all of the static components up-front - STREAM_ID = bytes_4(randint(0x00, 0xFFFFFFFF)) - SDP = _rf_src + _dst_id + _peer - LC = LC_OPT + _dst_id + _rf_src - - HEAD_LC = bptc.encode_header_lc(LC) - HEAD_LC = [HEAD_LC[:98], HEAD_LC[-98:]] - - TERM_LC = bptc.encode_terminator_lc(LC) - TERM_LC = [TERM_LC[:98], TERM_LC[-98:]] - - EMB_LC = bptc.encode_emblc(LC) - - EMBED = [] - EMBED.append( BS_VOICE_SYNC ) - EMBED.append(EMB['BURST_B'][:8] + EMB_LC[1] + EMB['BURST_B'][-8:]) - EMBED.append(EMB['BURST_C'][:8] + EMB_LC[2] + EMB['BURST_C'][-8:]) - EMBED.append(EMB['BURST_D'][:8] + EMB_LC[3] + EMB['BURST_D'][-8:]) - EMBED.append(EMB['BURST_E'][:8] + EMB_LC[4] + EMB['BURST_E'][-8:]) - EMBED.append(EMB['BURST_F'][:8] + NULL_EMB_LC + EMB['BURST_F'][-8:]) - - - #initialize the HBP calls stream sequence to 0 - SEQ = 0 - - # Send the Call Stream - - # Send 3 Voice Header Frames - for i in range(3): - pkt = b'DMRD' + bytes([SEQ]) + SDP + bytes([_slot << 7 | HEADBITS]) + STREAM_ID + (HEAD_LC[0] + SLOT_TYPE['VOICE_LC_HEAD'][:10] + BS_DATA_SYNC + SLOT_TYPE['VOICE_LC_HEAD'][-10:] + HEAD_LC[1]).tobytes() + TAIL - SEQ = (SEQ + 1) % 0x100 - yield pkt - - # Send each burst, six bursts per Superframe rotating through with the proper EMBED value per burst A-F - for word in _phrase: - for burst in range(0, len(word)): - pkt = b'DMRD' + bytes([SEQ]) + SDP + bytes([_slot << 7 | BURSTBITS[burst % 6]]) + STREAM_ID + (word[burst + 0][0] + EMBED[burst % 6] + word[burst + 0][1]).tobytes() + TAIL - SEQ = (SEQ + 1) % 0x100 - yield pkt - - # Send a single Voice Terminator Frame - pkt = b'DMRD' + bytes([SEQ]) + SDP + bytes([_slot << 7 | TERMBITS]) + STREAM_ID + (TERM_LC[0] + SLOT_TYPE['VOICE_LC_TERM'][:10] + BS_DATA_SYNC + SLOT_TYPE['VOICE_LC_TERM'][-10:] + TERM_LC[1]).tobytes() + TAIL - SEQ = (SEQ + 1) % 0x100 - yield pkt - - if SEQ == 255: - SEQ = 0 - - # Return False to indicate we're done. - return False - - -if __name__ == '__main__': - from time import time - - speech = pkt_gen(bytes_3(3120101), bytes_3(3120), bytes_4(312000), 0, [words['all_circuits'], words['all_circuits']]) - - - while True: - try: - pkt = next(speech) - except StopIteration: - break - print(len(pkt), pkt[4], pkt) diff --git a/playback.py b/playback.py index cb9d1ba..6f206ba 100755 --- a/playback.py +++ b/playback.py @@ -33,12 +33,12 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main freedmr module -from freedmr import HBSYSTEM, systems, freedmr_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases +from FreeDMR.freedmr import HBSYSTEM, systems, freedmr_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases from dmr_utils3.utils import bytes_3, bytes_4, int_id, get_alias from dmr_utils3 import decode, bptc, const -import config -import log -import const +import FreeDMR.Config.config as config +import FreeDMR.Utilities.log as log +from FreeDMR.Const.const import * # The module needs logging logging, but handlers, etc. are controlled by the parent import logging