more tidying up

rate
Simon 4 years ago
parent f390965ac4
commit 7280f835d7
No known key found for this signature in database
GPG Key ID: 6ACF11630B5D8F82

@ -42,8 +42,8 @@ from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task from twisted.internet import reactor, task
# Other files we pull from -- this is mostly for readability and segmentation # Other files we pull from -- this is mostly for readability and segmentation
import log import FreeDMR.Utilities.log as log
import config import FreeDMR.Config.config as config
from FreeDMR.Const.const import * from FreeDMR.Const.const import *
from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict

@ -42,8 +42,8 @@ from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task from twisted.internet import reactor, task
# Other files we pull from -- this is mostly for readability and segmentation # Other files we pull from -- this is mostly for readability and segmentation
import log import FreeDMR.Utilities.log as log
import config import FreeDMR.Config.config as config
from FreeDMR.Const.const import * from FreeDMR.Const.const import *
from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict

@ -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())

@ -1,175 +0,0 @@
###############################################################################
# Copyright (C) 2020 Simon Adlem, G7RZU <g7rzu@gb7fr.org.uk>
#
# 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'))

@ -42,8 +42,8 @@ from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task from twisted.internet import reactor, task
# Other files we pull from -- this is mostly for readability and segmentation # Other files we pull from -- this is mostly for readability and segmentation
import log import FreeDMR.Utilities.log as log
import config import FreeDMR.Config.config as config
from FreeDMR.Const.const import * from FreeDMR.Const.const import *
from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict from dmr_utils3.utils import int_id, bytes_4, try_download, mk_id_dict

@ -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 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.utils import bytes_3, int_id, get_alias
from dmr_utils3 import decode, bptc, const from dmr_utils3 import decode, bptc, const
import config import FreeDMR.Config.config as config
import log import FreeDMR.Utilities.log as log
from FreeDMR.Const.const import * from FreeDMR.Const.const import *
# Stuff for socket reporting # Stuff for socket reporting

@ -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 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.utils import bytes_3, int_id, get_alias, bytes_4
from dmr_utils3 import decode, bptc, const from dmr_utils3 import decode, bptc, const
import config import FreeDMR.Config.config as config
from config import acl_build from FreeDMR.Config.config import acl_build
import log import FreeDMR.Utilities.log as log
from FreeDMR.Const.const import * from FreeDMR.Const.const import *
from mk_voice import pkt_gen from FreeDMR.AMBE.mk_voice import pkt_gen
#from voice_lib import words #from voice_lib import words
#Read voices #Read voices
from FreeDMR.Utilities.read_ambe import readAMBE from FreeDMR.AMBE.read_ambe import readAMBE
#Remap some words for certain languages #Remap some words for certain languages
from FreeDMR.i8n.i8n_voice_map import voiceMap from FreeDMR.i8n.i8n_voice_map import voiceMap
#MySQL #MySQL
from FreeDMR.Utilities.mysql_config import useMYSQL from FreeDMR.Config.mysql_config import useMYSQL
# Stuff for socket reporting # Stuff for socket reporting
import pickle import pickle

@ -1,408 +0,0 @@
#!/usr/bin/env python
#
###############################################################################
# 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
# 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']))

@ -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'

@ -1,93 +0,0 @@
#!/usr/bin/env python
#
###############################################################################
# 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
# 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'])

@ -1,107 +0,0 @@
#!/usr/bin/env python
#
###############################################################################
# Copyright (C) 2016-2019 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
###############################################################################
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)

@ -33,12 +33,12 @@ from twisted.protocols.basic import NetstringReceiver
from twisted.internet import reactor, task from twisted.internet import reactor, task
# Things we import from the main freedmr module # 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.utils import bytes_3, bytes_4, int_id, get_alias
from dmr_utils3 import decode, bptc, const from dmr_utils3 import decode, bptc, const
import config import FreeDMR.Config.config as config
import log import FreeDMR.Utilities.log as log
import const from FreeDMR.Const.const import *
# The module needs logging logging, but handlers, etc. are controlled by the parent # The module needs logging logging, but handlers, etc. are controlled by the parent
import logging import logging

Loading…
Cancel
Save

Powered by TurnKey Linux.