From c25e1b95a6a321590449e6b7a83b4696ff47cd83 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 27 Dec 2021 18:12:02 +0000 Subject: [PATCH] Revert "tidy up FreeDMR module and class structure" This reverts commit 3bbb970198b06a5ccefecf67a354a12e356beccb. --- bridge.py | 4 +- bridge_master.py | 10 +- config.py | 2 +- const.py | 78 ++++++++++ i8n_voice_map.py | 348 +++++++++++++++++++++++++++++++++++++++++++++ mysql_config.py | 107 ++++++++++++++ read_ambe.py | 175 +++++++++++++++++++++++ reporting_const.py | 30 ++++ 8 files changed, 746 insertions(+), 8 deletions(-) create mode 100755 const.py create mode 100644 i8n_voice_map.py create mode 100644 mysql_config.py create mode 100644 read_ambe.py create mode 100755 reporting_const.py diff --git a/bridge.py b/bridge.py index 1a66388..8657138 100755 --- a/bridge.py +++ b/bridge.py @@ -42,12 +42,12 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main freedmr module -from FreeDMR.freedmr import HBSYSTEM, OPENBRIDGE, systems, freedmr_handler, reportFactory, REPORT_OPCODES, mk_aliases +from 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 -from FreeDMR.Const.const import * +from const import * # Stuff for socket reporting import pickle diff --git a/bridge_master.py b/bridge_master.py index 211ff68..f8c6820 100644 --- a/bridge_master.py +++ b/bridge_master.py @@ -46,24 +46,24 @@ from twisted.protocols.basic import NetstringReceiver from twisted.internet import reactor, task # Things we import from the main freedmr module -from FreeDMR.freedmr import HBSYSTEM, OPENBRIDGE, systems, freedmr_handler, reportFactory, REPORT_OPCODES, mk_aliases, acl_check +from 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 -from FreeDMR.Const.const import * +from const import * from mk_voice import pkt_gen #from voice_lib import words #Read voices -from FreeDMR.Utilities.read_ambe import readAMBE +from read_ambe import readAMBE #Remap some words for certain languages -from FreeDMR.i8n.i8n_voice_map import voiceMap +from i8n_voice_map import voiceMap #MySQL -from FreeDMR.Utilities.mysql_config import useMYSQL +from mysql_config import useMYSQL # Stuff for socket reporting import pickle diff --git a/config.py b/config.py index 81964f3..11923b8 100755 --- a/config.py +++ b/config.py @@ -28,7 +28,7 @@ change. import configparser import sys -import FreeDMR.Const.const as const +import const import socket import ipaddress diff --git a/const.py b/const.py new file mode 100755 index 0000000..c491223 --- /dev/null +++ b/const.py @@ -0,0 +1,78 @@ +#!/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 +############################################################################### + +''' +These are contants used by HBlink. Rather than stuff them into the main program +file, any new constants should be placed here. It makes them easier to keep track +of and keeps hblink.py shorter. +''' + +__author__ = 'Cortney T. Buffington, N0MJS' +__copyright__ = 'Copyright (c) 2016 Cortney T. Buffington, N0MJS and the K0USY Group' +__credits__ = '' +__license__ = 'GNU GPLv3' +__maintainer__ = 'Cort Buffington, N0MJS' +__email__ = 'n0mjs@me.com' + + +# DMR Related constants +ID_MIN = 1 +ID_MAX = 16776415 + +# Timers +STREAM_TO = .360 + +# Options from the LC - used for late entry +LC_OPT = b'\x00\x00\x20' + +# HomeBrew Protocol Frame Types +HBPF_VOICE = 0x0 +HBPF_VOICE_SYNC = 0x1 +HBPF_DATA_SYNC = 0x2 +HBPF_SLT_VHEAD = 0x1 +HBPF_SLT_VTERM = 0x2 + +# HomeBrew Protocol Commands +DMRD = b'DMRD' +MSTCL = b'MSTCL' +MSTNAK = b'MSTNAK' +MSTPONG = b'MSTPONG' +MSTN = b'MSTN' +MSTP = b'MSTP' +MSTC = b'MSTC' +RPTL = b'RPTL' +RPTPING = b'RPTPING' +RPTCL = b'RPTCL' +RPTL = b'RPTL' +RPTACK = b'RPTACK' +RPTK = b'RPTK' +RPTC = b'RPTC' +RPTP = b'RPTP' +RPTA = b'RPTA' +RPTO = b'RPTO' +DMRA = b'DMRA' + +#Bridge Control commands +BC = b'BC' +BCKA = b'BCKA' +BCSQ = b'BCSQ' + +# Higheset peer ID permitted by HBP +PEER_MAX = 4294967295 diff --git a/i8n_voice_map.py b/i8n_voice_map.py new file mode 100644 index 0000000..401d2c1 --- /dev/null +++ b/i8n_voice_map.py @@ -0,0 +1,348 @@ +############################################################################### +# 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 +############################################################################### +voiceMap = { + 'en_GB': { + 'A': 'alpha', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + + + 'en_GB_2': { + 'A': 'alpha', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + 'cy_GB': { + 'A': 'alpha', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + + 'en_US': { + 'to': '2', + 'freedmr': 'silence', + 'this-is': 'silence' + }, + + 'es_ES': { + '1': 'one', + '2': 'two', + '3': 'three', + '4': 'four', + '5': 'five', + '6': 'six', + '7': 'seven', + '8': 'eight', + '9': 'nine', + 'A': 'alfa', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + 'es_ES_2': { + '1': 'one', + '2': 'two', + '3': 'three', + '4': 'four', + '5': 'five', + '6': 'six', + '7': 'seven', + '8': 'eight', + '9': 'nine', + 'A': 'alfa', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + + 'fr_FR': { + 'A': 'alpha', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + + 'pt_PT': { + 'A': 'alpha', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + + 'el_GR': { + 'A': 'alpha', + 'B': 'bravo', + 'C': 'charlie', + 'D': 'delta', + 'E': 'echo', + 'F': 'foxtrot', + 'G': 'golf', + 'H': 'hotel', + 'I': 'india', + 'J': 'juliet', + 'K': 'kilo', + 'L': 'lima', + 'M': 'mike', + 'N': 'november', + 'O': 'oscar', + 'P': 'papa', + 'Q': 'quebec', + 'R': 'romeo', + 'S': 'sierra', + 'T': 'tango', + 'U': 'uniform', + 'V': 'victor', + 'W': 'whiskey', + 'X': 'x-ray', + 'Y': 'yankee', + 'Z': 'zulu', + 'to': 'silence', + 'notlinked': 'not-linked', + 'linkedto': 'linked-to' + }, + + 'de_DE': { + + 'to': 'silence', + }, + + 'dk_DK': { + 'to': 'silence', + 'freedmr': 'silence', + 'this-is': 'silence' + + }, + + 'it_IT': { + 'to': 'silence', + 'freedmr': 'silence', + 'this-is': 'silence' + }, + + 'no_NO': { + 'to': 'silence', + 'freedmr': 'silence', + 'this-is': 'silence' + }, + + 'pl_PL': { + 'to': 'silence', + 'freedmr': 'silence', + 'this-is': 'silence' + }, + + 'se_SE': { + 'to': 'silence', + 'freedmr': 'silence', + 'this-is': 'silence' + }, + + 'CW': { + 'to': 'silence', + 'freedmr': 'silence', + 'this-is': 'silence', + 'linkedto': 'silence' + }, + + 'th_TH': { + + 'to': 'silence', + }, + + +} + diff --git a/mysql_config.py b/mysql_config.py new file mode 100644 index 0000000..fe8553b --- /dev/null +++ b/mysql_config.py @@ -0,0 +1,107 @@ +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/read_ambe.py b/read_ambe.py new file mode 100644 index 0000000..0442aba --- /dev/null +++ b/read_ambe.py @@ -0,0 +1,175 @@ +############################################################################### +# 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/reporting_const.py b/reporting_const.py new file mode 100755 index 0000000..332e515 --- /dev/null +++ b/reporting_const.py @@ -0,0 +1,30 @@ +############################################################################### +# Copyright (C) 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 +############################################################################### + +# Opcodes for the network-based reporting protocol + +REPORT_OPCODES = { + 'CONFIG_REQ': b'\x00', + 'CONFIG_SND': b'\x01', + 'BRIDGE_REQ': b'\x02', + 'BRIDGE_SND': b'\x03', + 'CONFIG_UPD': b'\x04', + 'BRIDGE_UPD': b'\x05', + 'LINK_EVENT': b'\x06', + 'BRDG_EVENT': b'\x07', + }