diff --git a/.gitignore b/.gitignore index b86d23a..68f8cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ **/._.DS_Store *.log *.ini +**/logs/* \ No newline at end of file diff --git a/BotResources.py b/BotResources.py index 17939b4..b8b8c43 100644 --- a/BotResources.py +++ b/BotResources.py @@ -1,4 +1,7 @@ import configparser +import logging +import os +from datetime import date from os.path import exists from NoiseGatev2 import AudioStream @@ -12,6 +15,8 @@ PDB_KNOWN_BOT_IDS = {756327271597473863: "Greada", 915064996994633729: "Jorn", 9 DEFAULT_NOISEGATE_THRESHOLD = 50 +LOGGER = logging.getLogger('Discord_Radio_Bot.Bot_Resources') + def check_if_config_exists(): if exists('./config.ini'): @@ -38,9 +43,9 @@ def read_config_file(): 'Handler': str(config['Config']['Handler']) } - print("Found config options:") + LOGGER.debug("Found config options:") for key in config_return.keys(): - print(f"\t{key} : {config_return[key]}") + LOGGER.debug(f"\t{key} : {config_return[key]}") return config_return @@ -91,14 +96,14 @@ def write_config_file(**kwargs): with open('./config.ini', 'w') as config_file: config.write(config_file) - print("Config Saved") + LOGGER.info("Config Saved") return True def get_device_list(): - list_of_devices = sound.query_devices().items() - print(list_of_devices) + list_of_devices = AudioStream().list_devices() + LOGGER.debug(list_of_devices) return list_of_devices @@ -106,38 +111,38 @@ def get_user_device_selection(): device_list = get_device_list() org_device_list = [] for device, dev_id in device_list: - print(f"{dev_id + 1}\t-\t{device}") + LOGGER.debug(f"{dev_id + 1}\t-\t{device}") org_device_list.append((dev_id, device)) selected_list_id = None selected_device = None while not selected_list_id: - print(f"selected device: {selected_list_id}") - print(device_list) + LOGGER.debug(f"selected device: {selected_list_id}") + LOGGER.debug(device_list) try: selected_list_id = int(input(f"Please select the input device from above:\t")) except Exception as e: - print(e) + LOGGER.warning(e) continue if int(selected_list_id) < int(len(device_list)): - print("ford") + LOGGER.debug("Selected ID within range") continue elif selected_list_id > int(len(device_list)): - print("Out of range, try again...") + LOGGER.debug("Out of range, try again...") selected_list_id = None continue else: selected_list_id = None - print("Internal error, try again") + LOGGER.error("Internal error, try again") continue for dev_dict in org_device_list: - print(list(device_list)[selected_list_id-1][0]) + LOGGER.debug(list(device_list)[selected_list_id-1][0]) if dev_dict[1] == list(device_list)[selected_list_id-1][0]: selected_device = dev_dict - print(selected_device) + LOGGER.debug(selected_device) return selected_device @@ -149,7 +154,7 @@ def get_user_token(): if len(token) == 59: return token else: - print('Length error in token, please try again...') + LOGGER.error('Length error in token, please try again...') token = None continue @@ -168,7 +173,7 @@ def get_user_mention_channel_id(): if len(str(channel_id)) == len('757379843792044102'): return channel_id else: - print("Length error in ID, please try again") + LOGGER.error("Length error in ID, please try again") channel_id = None continue @@ -204,3 +209,39 @@ async def check_and_reply_to_ping(bot, message): else: await bot.process_commands(message) return False + + +# Create the logger +def init_global_logger(_verbose_level: str = "WARNING"): + numeric_log_level = getattr(logging, _verbose_level.upper(), None) + if not isinstance(numeric_log_level, int): + raise ValueError('Invalid log level: %s' % _verbose_level) + else: + # create logger + init_logger = logging.getLogger('Discord_Radio_Bot') + init_logger.setLevel(logging.DEBUG) + + # create file handler which logs even debug messages + if not exists("./logs/"): + os.mkdir("./logs/") + fh = logging.FileHandler(f'./logs/DRB-{date.today()}.log') + fh.setLevel(logging.DEBUG) + + # create terminal handler with a higher log level + th = logging.StreamHandler() + th.setLevel(numeric_log_level) + + # create formatter and add it to the handlers + fh_formatter = logging.Formatter('[%(asctime)s %(name)s->%(funcName)s():%(lineno)s] - %(levelname)s - %(message)s') + th_formatter = logging.Formatter('[%(asctime)s %(name)s->%(funcName)s()] - %(levelname)s - %(message)s') + fh.setFormatter(fh_formatter) + th.setFormatter(th_formatter) + + # add the handlers to the logger + init_logger.addHandler(fh) + init_logger.addHandler(th) + + +#if __name__ is not 'main': +# init_global_logger() +# LOGGER = logging.getLogger('Discord_Radio_Bot') diff --git a/NoiseGatev2.py b/NoiseGatev2.py index a87fb43..96a8db0 100644 --- a/NoiseGatev2.py +++ b/NoiseGatev2.py @@ -1,12 +1,18 @@ import audioop +import logging import math +import time + import pyaudio import discord import numpy voice_connection = None +LOGGER = logging.getLogger("Discord_Radio_Bot.NoiseGateV2") + +# noinspection PyUnresolvedReferences class AudioStream: def __init__(self, _channels: int = 2, _sample_rate: int = 48000, _frames_per_buffer: int = 1024, _input_device_index: int = None, _output_device_index: int = None, _input: bool = True, @@ -24,19 +30,19 @@ class AudioStream: if _input: self.paInstance_kwargs['input_device_index'] = _input_device_index else: - print(f"[AudioStream.__init__]:\tInput was not enabled." - f" Reinitialize with '_input=True'") + LOGGER.warning(f"[AudioStream.__init__]:\tInput was not enabled." + f" Reinitialize with '_input=True'") if _output_device_index: if _output: self.paInstance_kwargs['output_device_index'] = _output_device_index else: - print(f"[AudioStream.__init__]:\tOutput was not enabled." - f" Reinitialize with '_output=True'") + LOGGER.warning(f"[AudioStream.__init__]:\tOutput was not enabled." + f" Reinitialize with '_output=True'") if _init_on_startup: # Init PyAudio instance - print("Creating PyAudio instance") + LOGGER.info("Creating PyAudio instance") self.paInstance = pyaudio.PyAudio() # Define and initialize stream object if we have been passed a device ID (pyaudio.open) @@ -44,7 +50,7 @@ class AudioStream: if _output_device_index or _input_device_index: if _init_on_startup: - print("Init stream") + LOGGER.info("Init stream") self.init_stream() def init_stream(self, _new_output_device_index: int = None, _new_input_device_index: int = None): @@ -53,15 +59,15 @@ class AudioStream: if self.paInstance_kwargs['input']: self.paInstance_kwargs['input_device_index'] = _new_input_device_index else: - print(f"[AudioStream.init_stream]:\tInput was not enabled when initialized." - f" Reinitialize with '_input=True'") + LOGGER.warning(f"[AudioStream.init_stream]:\tInput was not enabled when initialized." + f" Reinitialize with '_input=True'") if _new_output_device_index: if self.paInstance_kwargs['output']: self.paInstance_kwargs['output_device_index'] = _new_output_device_index else: - print(f"[AudioStream.init_stream]:\tOutput was not enabled when initialized." - f" Reinitialize with '_output=True'") + LOGGER.warning(f"[AudioStream.init_stream]:\tOutput was not enabled when initialized." + f" Reinitialize with '_output=True'") self.close_if_open() @@ -74,7 +80,7 @@ class AudioStream: if self.stream.is_active(): self.stream.stop_stream() self.stream.close() - print(f"[ReopenStream.close_if_open]:\t Stream was open; It was closed.") + LOGGER.debug(f"[ReopenStream.close_if_open]:\t Stream was open; It was closed.") def list_devices(self, _display_input_devices: bool = True, _display_output_devices: bool = True): info = self.paInstance.get_host_api_info_by_index(0) @@ -89,13 +95,13 @@ class AudioStream: input_device = self.paInstance.get_device_info_by_host_api_device_index(0, i).get('name') devices['Input'][i] = input_device if _display_input_devices: - print("Input Device id ", i, " - ", input_device) + LOGGER.debug("Input Device id ", i, " - ", input_device) if (self.paInstance.get_device_info_by_host_api_device_index(0, i).get('maxOutputChannels')) > 0: output_device = self.paInstance.get_device_info_by_host_api_device_index(0, i).get('name') devices['Output'][i] = output_device if _display_output_devices: - print("Output Device id ", i, " - ", output_device) + LOGGER.debug("Output Device id ", i, " - ", output_device) return devices @@ -106,6 +112,7 @@ class AudioStream: self.paInstance.terminate() +# noinspection PyUnresolvedReferences class NoiseGate(AudioStream): def __init__(self, _voice_connection, _noise_gate_threshold: int, **kwargs): super(NoiseGate, self).__init__(_init_on_startup=True, **kwargs) @@ -113,25 +120,43 @@ class NoiseGate(AudioStream): voice_connection = _voice_connection self.THRESHOLD = _noise_gate_threshold self.NGStream = NoiseGateStream(self) + self.Voice_Connection_Thread = None def run(self) -> None: global voice_connection # Start the audio stream + LOGGER.debug(f"Starting stream") self.stream.start_stream() - voice_connection.play(self.NGStream) + # Start the stream to discord + self.core() + + def core(self, error=None): + if error: + LOGGER.warning(error) + + while not voice_connection.is_connected(): + time.sleep(.2) + + if not voice_connection.is_playing(): + LOGGER.debug(f"Playing stream to discord") + voice_connection.play(self.NGStream, after=self.core) async def close(self): + LOGGER.debug(f"Closing") await voice_connection.disconnect() if self.stream.is_active: self.stream.stop_stream() + LOGGER.debug(f"Stopping stream") + +# noinspection PyUnresolvedReferences class NoiseGateStream(discord.AudioSource): def __init__(self, _stream): super(NoiseGateStream, self).__init__() self.stream = _stream # The actual audio stream object self.NG_fadeout = 240/20 # Fadeout value used to hold the noisegate after de-triggering self.NG_fadeout_count = 0 # A count set when the noisegate is triggered and was de-triggered - self.process_set_count = 0 # Counts how many processes have been made in order to limit the prints + self.process_set_count = 0 # Counts how many processes have been made def read(self): try: @@ -142,7 +167,7 @@ class NoiseGateStream(discord.AudioSource): buffer_decibel = 20 * math.log10(buffer_rms) if self.process_set_count % 10 == 0: - print(f"{buffer_decibel} db") + LOGGER.debug(f"{buffer_decibel} db") if buffer_decibel >= self.stream.THRESHOLD: self.NG_fadeout_count = self.NG_fadeout @@ -153,14 +178,13 @@ class NoiseGateStream(discord.AudioSource): else: if self.NG_fadeout_count > 0: self.NG_fadeout_count -= 1 - print(f"Frames in fadeout remaining: {self.NG_fadeout_count}") + LOGGER.debug(f"Frames in fadeout remaining: {self.NG_fadeout_count}") self.process_set_count += 1 if curr_buffer: - if self.NG_fadeout_count == 0: - voice_connection.stop() return bytes(curr_buffer) except OSError as e: + LOGGER.warning(e) pass def audio_datalist_set_volume(self, datalist, volume): diff --git a/README.md b/README.md index 6fa1a10..77a0b3f 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ This project is intended to allow users in discord to be able to listen to their ## Setup 1. Install the pip packages found in the ```Requirements.txt``` file -2. Run ```main.py``` with Python -3. Follow the prompts in the terminal -4. Ensure your audio is playing on the selected device -5. You're all set! Ask the bot to join! + - *Optional if you are using OP25* Run `echo 0 > /sys/module/usbcore/parameters/usbfs_memory_mb` +3. Run ```main.py``` with Python +4. Follow the prompts in the terminal +5. Ensure your audio is playing on the selected device +5You're all set! Ask the bot to join! ### Understanding Audio Input This title can be a bit confusing. The bot will display both 'input' and 'output' devices but not always *all* devices connected. diff --git a/TODO.md b/TODO.md index d763e7a..4b281c7 100644 --- a/TODO.md +++ b/TODO.md @@ -1,21 +1,47 @@ -## To-Do -### Main Bot +# To-Do +## Main Bot +### Main Development +#### Core +- [ ] Add new handlers for GQRX: https://github.com/gqrx-sdr/gqrx/blob/master/resources/remote-control.txt + - [ ] Add a process handler to start/stop gqrx - [ ] Fix the bug where they *disconnect* after a period of time and must be manually moved out and back in to hear them - *May* have been fixed with the noise gate? - [ ] Fix bug that shows different index number for audio device selection on linux + - [ ] New bug on linux shows a bunch of ALSA output - [x] Add more proper help text with real examples for discord commands - [x] Add command line args with argparse for main bot - [x] Add method for user to change audio device without redoing entire config file +- [ ] Need to create a method for the bot to toggle debug mode for OP25 + +#### Features +- [ ] Add to a linux service +- [ ] Add a function to the bot to allow *authorized* users to; update, restart, git pull, etc. + +### Polishing - [ ] Clean up code -- [ ] Transcode radio transmissions to text -- [ ] Need to create toggle/selection for OP25 debug mode -### Modules -#### Willie Timer +- [ ] Revise discord help text +- [ ] Revise logging + +### Final +- [ ] Create setup script + - [ ] Test setup +- [ ] Revise readme + +-------------------------------------------- +## Modules +### Willie Timer +- [ ] Revise the current loop, ensure it re-enters itself after triggering + - [ ] Can we use a cron somehow? - [ ] Get more training data for WillieTimer - [ ] Use the ```Phrases.txt``` file as the random seed? - - [ ] Figure out a way to give the model a suggestion + - [ ] Figure out a way to give the model a suggestion -### Done Previously +------------------------------- +## Dreams +- [ ] Transcode radio transmissions to text + +--------------------- +## Done Previously - [x] Interact with soapysdr directly from the bot - [x] Allow chat interaction with soapysdr - [x] Move cogs to their own files diff --git a/bot.py b/bot.py index e30a7fd..c33f2b6 100644 --- a/bot.py +++ b/bot.py @@ -1,4 +1,5 @@ import asyncio +import logging import os import platform import discord @@ -17,6 +18,9 @@ class Bot(commands.Bot): commands.Bot.__init__(self, command_prefix=commands.when_mentioned_or(kwargs['command_prefix']), activity=discord.Game(name=f"@ me"), status=discord.Status.idle) + # Create the logger for the bot + self.logger = logging.getLogger("Discord_Radio_Bot.Bot") + # Init the core bot variables self.DEVICE_ID = kwargs['Device_ID'] self.DEVICE_NAME = kwargs['Device_Name'] @@ -82,7 +86,7 @@ class Bot(commands.Bot): brief="Joins the voice channel that the caller is in") async def join(ctx, *, member: discord.Member = None): member = member or ctx.author.display_name - print(f"Join requested by {member}") + self.logger.info(f"Join requested by {member}") if not self.Bot_Connected: # Wait for the bot to be ready to connect @@ -93,15 +97,15 @@ class Bot(commands.Bot): if discord.opus.is_loaded(): channel = ctx.author.voice.channel - print("Sending hello") + self.logger.debug("Sending hello") await ctx.send(f"Ok {str(member).capitalize()}, I'm joining {channel}") # Join the voice channel with the audio stream - print('Joining') + self.logger.debug('Joining') voice_connection = await channel.connect() # Create an audio stream from selected device - print("Starting noisegate/stream handler") + self.logger.debug("Starting noisegate/stream handler") self.streamHandler = NoiseGatev2.NoiseGate(_input_device_index=self.DEVICE_ID, _voice_connection=voice_connection, _noise_gate_threshold=self.noisegate_sensitivity) @@ -109,64 +113,66 @@ class Bot(commands.Bot): self.streamHandler.run() # Start the SDR and begin playing to the audio stream - print("Starting SDR") + self.logger.debug("Starting SDR") self.start_sdr() # Change the activity to the channel and band-type being used - print("Changing presence") + self.logger.debug("Changing presence") await self.set_activity() # 'Lock' the bot from connecting - print("Locking the bot") + self.logger.debug("Locking the bot") self.Bot_Connected = True else: # Return that the opus library would not load await ctx.send("Opus won't load") - print("OPUS didn't load properly") + self.logger.critical("OPUS didn't load properly") else: await ctx.send(f"{str(member).capitalize()}, I'm already connected") - print("Bot is already in a channel") + self.logger.info("Bot is already in a channel") @self.command(help="Use this command to have the bot leave your channel", brief="Leaves the current voice channel") async def leave(ctx, member: discord.Member = None): member = member or ctx.author.display_name - print(f"Leave requested by {member}") + self.logger.info(f"Leave requested by {member}") if self.Bot_Connected: # Stop the sound handlers # Disconnect the client from the voice channel - print("Disconnecting") + self.logger.debug("Disconnecting") await self.streamHandler.close() - print("Changing presence") + self.logger.debug("Changing presence") # Change the presence to away and '@ me' await self.set_activity(False) # Stop the SDR so it can cool off - print("Stopping SDR") + self.logger.debug("Stopping SDR") self.stop_sdr() - print("Unlocking the bot") + self.logger.debug("Unlocking the bot") # 'Unlock' the bot self.Bot_Connected = False - print("Sending Goodbye") + self.logger.debug("Sending Goodbye") await ctx.send(f"Goodbye {str(member).capitalize()}.") else: await ctx.send(f"{str(member).capitalize()}, I'm not in a channel") + self.logger.info("Bot is not in a channel") # Add command to change the NoiseGate threshold @self.command(help="Use this command to change the threshold of the noisegate", brief="Change noisegate sensitivity") async def chngth(ctx, _threshold: int, member: discord.Member = None): member = member or ctx.author.display_name - print(f"Change of NoiseGate threshold requested by {member}") + self.logger.info(f"Change of NoiseGate threshold requested by {member}") await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the threshold from " f"{self.streamHandler.THRESHOLD} to {_threshold}") self.streamHandler.THRESHOLD = _threshold + self.noisegate_sensitivity = _threshold if self.sdr_started: await self.set_activity() @@ -182,6 +188,7 @@ class Bot(commands.Bot): member = member or ctx.author.display_name message = self.display_current_radio_config() await ctx.send(f"Ok {str(member).capitalize()},\n{message}") + self.logger.info(f"Displaying current profile; requested by {member}") # Command to display the current config @self.command(name='displayprofiles', @@ -193,6 +200,7 @@ class Bot(commands.Bot): member = member or ctx.author.display_name message = self.display_saved_radio_configs() await ctx.send(f"Ok {str(member).capitalize()},\n{message}") + self.logger.info(f"Displaying all profiles; requested by {member}") # Command to change the current frequency and mode @self.command(name='chfreq', help="Use this command to change the frequency the bot is listening to.\n" @@ -205,6 +213,7 @@ class Bot(commands.Bot): async def chfreq(ctx, mode: str, freq: str, member: discord.Member = None): # Possible band-types that can be used member = member or ctx.author.display_name + self.logger.info(f"{member} requested change of frequency to Mode: {mode}, Freq: {freq}") # Check to make sure the frequency input matches the syntax needed if len(freq) >= 6: @@ -240,6 +249,7 @@ class Bot(commands.Bot): brief="Changes radio squelch") async def chsquelch(ctx, squelch: float, member: discord.Member = None): member = member or ctx.author.display_name + self.logger.info(f"{member} requested change of squelch to: {squelch}") self.squelch = squelch await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the squelch to {self.squelch}") @@ -289,13 +299,13 @@ class Bot(commands.Bot): async def on_ready(): # Check the ./modules folder for any modules (cog.py) await self.check_for_modules() - print("Bot started!") + self.logger.info("Bot started!") # Check to see if other bots are online async def check_other_bots_online(self): - print('Checking if other bots are online') + self.logger.info('Checking if other bots are online') channel = self.get_channel(self.Default_Channel_ID) - print(f"Testing in: {channel}") + self.logger.debug(f"Testing in: {channel}") bots_online = [] @@ -315,7 +325,7 @@ class Bot(commands.Bot): except asyncio.exceptions.TimeoutError: seconds_waited += 1 - print(f"Bots Online: {bots_online}") + self.logger.debug(f"Bots Online: {bots_online}") if len(bots_online) == 0: return False @@ -325,13 +335,13 @@ class Bot(commands.Bot): # Check the handler being used during init def check_handler(self): if self.Handler == "gqrx": - print("Starting GQRX handler") + self.logger.info("Starting GQRX handler") from gqrxHandler import GQRXHandler self.GQRXHandler = GQRXHandler() self.possible_modes = BotResources.PDB_ACCEPTABLE_HANDLERS['gqrx']['Modes'] elif self.Handler == 'op25': - print("Starting OP25 handler") + self.logger.info("Starting OP25 handler") from op25Handler import OP25Handler self.OP25Handler = OP25Handler() self.OP25Handler.start() @@ -342,22 +352,22 @@ class Bot(commands.Bot): # Check the system type and load the correct library # Linux ARM AARCH64 running 32bit OS if self.system_os_type == 'Linux_ARMv7l': - print(f"Loaded OPUS library for {self.system_os_type}") + self.logger.debug(f"Loaded OPUS library for {self.system_os_type}") discord.opus.load_opus('./opus/libopus_armv7l.so') # Linux ARM AARCH64 running 64bit OS if self.system_os_type == 'Linux_AARCH64': - print(f"Loaded OPUS library for {self.system_os_type}") + self.logger.debug(f"Loaded OPUS library for {self.system_os_type}") discord.opus.load_opus('./opus/libopus_aarcch64.so') # Windows 64bit if self.system_os_type == 'Windows_x64': - print(f"Loaded OPUS library for {self.system_os_type}") + self.logger.debug(f"Loaded OPUS library for {self.system_os_type}") discord.opus.load_opus('./opus/libopus_amd64.dll') # Check to make sure the selected device is still available and has not changed its index def check_device(self, _override): # Check to see if an override has been passed if not _override: - print(f"Device list {self.Devices_List}") + self.logger.debug(f"Device list {self.Devices_List}") for device, index in self.Devices_List['Input']: if int(index) == self.DEVICE_ID and str(device) == self.DEVICE_NAME: return True @@ -382,19 +392,19 @@ class Bot(commands.Bot): if str(folder_name)[0] == '.': continue elif os.path.exists(os.path.join("modules", folder_name, "cog.py")): - print(f"Loaded extension: {folder_name}") + self.logger.debug(f"Loaded extension: {folder_name}") self.load_extension(f"modules.{folder_name}.cog") # Reload a selected module for changes def reload_modules(self, module): try: self.unload_extension(f"modules.{module}.cog") - print(f"Unloaded {module}") + self.logger.debug(f"Unloaded {module}") self.load_extension(f"modules.{module}.cog") - print(f"Loaded {module}") + self.logger.debug(f"Loaded {module}") return True except Exception as e: - print(e) + self.logger.error(e) return False # Check and store the OS type of the system for later use @@ -403,14 +413,14 @@ class Bot(commands.Bot): if os.name == 'nt': if processor == "AMD64": self.system_os_type = 'Windows_x64' - print(f"OS/Arch is {self.system_os_type}") + self.logger.debug(f"OS/Arch is {self.system_os_type}") else: if processor == "aarch64": self.system_os_type = 'Linux_AARCH64' - print(f"OS/Arch is {self.system_os_type}") + self.logger.debug(f"OS/Arch is {self.system_os_type}") elif processor == "armv7l": self.system_os_type = 'Linux_ARMv7l' - print(f"OS/Arch is {self.system_os_type}") + self.logger.debug(f"OS/Arch is {self.system_os_type}") # Check to see if there is only one frequency def start_sdr(self): @@ -421,14 +431,14 @@ class Bot(commands.Bot): self.stop_sdr() # Start the radio - print(f"Starting freq: {self.freq}") + self.logger.debug(f"Starting freq: {self.freq}") if self.Handler == 'gqrx': # Set the settings in GQRX self.GQRXHandler.set_all_settings(self.mode, self.squelch, self.freq) elif self.Handler == 'op25': - self.OP25Handler.set_op25_parameters(self.freq, _start=True) + self.OP25Handler.set_op25_parameters(self.freq, _start=True, _output_device_name=self.DEVICE_NAME) # Set the started variable for later checks self.sdr_started = True @@ -466,7 +476,7 @@ class Bot(commands.Bot): # Save the current radio settings as a profile async def save_radio_config(self, _profile_name: str): - print(f"Saving profile {_profile_name}") + self.logger.debug(f"Saving profile {_profile_name}") config = configparser.SafeConfigParser() if os.path.exists('./profiles.ini'): @@ -493,7 +503,7 @@ class Bot(commands.Bot): # Load a saved profile into the current settings async def load_radio_config(self, profile_name): - print(f"Loading profile {profile_name}") + self.logger.debug(f"Loading profile {profile_name}") config = configparser.ConfigParser() if os.path.exists('./profiles.ini'): config.read('./profiles.ini') @@ -505,8 +515,9 @@ class Bot(commands.Bot): try: self.noisegate_sensitivity = int(config[self.profile_name]['Noisegate_Sensitivity']) except KeyError: - print(f"Config does not contain a 'noisegate sensitivity' value, " - f"creating one now with the default value: {BotResources.DEFAULT_NOISEGATE_THRESHOLD}") + self.logger.warning(f"Config does not contain a 'noisegate sensitivity' value, " + f"creating one now with the default value: " + f"{BotResources.DEFAULT_NOISEGATE_THRESHOLD}") self.noisegate_sensitivity = BotResources.DEFAULT_NOISEGATE_THRESHOLD await self.save_radio_config(self.profile_name) @@ -544,7 +555,7 @@ class Bot(commands.Bot): try: message_body += f"\tNoisegate Sensitivity:\t{config[section]['Noisegate_Sensitivity']}\n" except KeyError: - print(f"Config does not contain a 'noisegate sensitivity' value. Please load the profile") + self.logger.warning(f"Config does not contain a 'noisegate sensitivity' value. Please load the profile") message_body += f"\tSquelch:\t\t\t\t{config[section]['Squelch']}\n" diff --git a/gqrxHandler.py b/gqrxHandler.py index 9e5eafb..2727a13 100644 --- a/gqrxHandler.py +++ b/gqrxHandler.py @@ -1,9 +1,11 @@ +import logging from telnetlib import Telnet from BotResources import check_negative from time import sleep class GQRXHandler(): def __init__(self, hostname: str = "localhost", port: int = 7356): + self.logger = logging.getLogger("Discord_Radio_Bot.GQRXHandler") self.hostname = hostname self.port = port @@ -12,24 +14,24 @@ class GQRXHandler(): self.create_telnet_connection() def create_telnet_connection(self): - print("Creating connection") + self.logger.info("Creating connection") self.tel_conn = Telnet(self.hostname, self.port) self.tel_conn.open(self.hostname, self.port) def change_freq(self, freq): - print(f"Changing freq to {freq}") + self.logger.debug(f"Changing freq to {freq}") self.tel_conn.write(bytes(f"F {int(freq)}", 'utf-8')) self.tel_conn.read_until(b'RPRT 0') def change_squelch(self, squelch): if not check_negative(squelch): squelch = float(-abs(squelch)) - print(f"Changing squelch to {squelch}") + self.logger.debug(f"Changing squelch to {squelch}") self.tel_conn.write(bytes(f"L SQL {float(squelch)}", 'utf-8')) self.tel_conn.read_until(b'RPRT 0') def change_mode(self, mode): - print(f"Changing mode to {mode}") + self.logger.debug(f"Changing mode to {mode}") self.tel_conn.write(bytes(f"M {str(mode)}", 'utf-8')) self.tel_conn.read_until(b'RPRT 0') diff --git a/main.py b/main.py index 8887442..88aa9af 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,9 @@ +import logging import os import time import bot import argparse -from BotResources import check_if_config_exists, write_config_file, read_config_file +import BotResources # Jorn #token = 'OTE1MDY0OTk2OTk0NjMzNzI5.YaWKsA.Y9yaCGg_VXRL_qQVbs05vo7gSAc' @@ -15,26 +16,28 @@ from BotResources import check_if_config_exists, write_config_file, read_config_ #name = "VoiceMeeter Output" +LOGGER = logging.getLogger("Discord_Radio_Bot.Main") + class BotDeviceNotFound(Exception): def __init__(self, device): - print(f"Unable to find the device: {device}") + LOGGER.debug(f"Unable to find the device: {device}") try: os.remove('./config.ini') except OSError: - print("Config file not found, restarting.") + LOGGER.warning("Config file not found, restarting.") #os.execv(__file__, sys.argv) def main(**passed_config): # Don't create a config if the user passed parameters if len(passed_config.keys()) == 0: - print('Checking config file...') - if not check_if_config_exists(): - print("No config file exists, please enter this information now") - write_config_file(init=True) + LOGGER.info('Checking config file...') + if not BotResources.check_if_config_exists(): + LOGGER.warning("No config file exists, please enter this information now") + BotResources.write_config_file(init=True) - config = read_config_file() + config = BotResources.read_config_file() # Overwrite config options if they were passed if len(passed_config.keys()) == 0: @@ -43,13 +46,13 @@ def main(**passed_config): if sub in passed_config and passed_config[sub]: config[sub] = passed_config[sub] - print('Starting Bot...') + LOGGER.info('Starting Bot...') discord_bot_client = bot.Bot(Token=config['Bot Token'], Device_ID=config['Device ID'], Device_Name=config['Device Name'], Mention_Group=config['Mention Group'], Channel_ID=config['Channel ID'], Handler=config['Handler']) - print(f"Verifying audio device:\t{config['Device Name']}") + LOGGER.debug(f"Verifying audio device:\t{config['Device Name']}") if not discord_bot_client.check_device(_override=bool("Device ID" in passed_config.keys())): raise BotDeviceNotFound(config['Device Name']) @@ -112,20 +115,21 @@ def cmd_arguments(): if __name__ == '__main__': cmd_args = cmd_arguments() + BotResources.init_global_logger() if not all(cmd_args.values()): - print("Passed arguments:") + LOGGER.debug("Passed arguments:") for arg in cmd_args: if cmd_args[arg]: - print(f"\t{arg}:\t{cmd_args[arg]}") + LOGGER.debug(f"\t{arg}:\t{cmd_args[arg]}") try: - print('Starting...') + LOGGER.info('Starting...') while True: try: main(**cmd_args) except BotDeviceNotFound: - print("Restarting...") + LOGGER.info("Restarting...") time.sleep(2) except KeyboardInterrupt: - print("Exiting...") + LOGGER.error("Exiting...") diff --git a/modules/LinkCop/cog.py b/modules/LinkCop/cog.py index c180511..79e8f34 100644 --- a/modules/LinkCop/cog.py +++ b/modules/LinkCop/cog.py @@ -1,3 +1,4 @@ +import logging import re import discord from discord.ext import commands @@ -26,6 +27,8 @@ random_message = ["{%mtn_user%}, tsk tsk. Links belong here:\n{%ref_og_msg%}", " # welcome 757379843792044102 # the-round-table 367396189529833474 +LOGGER = logging.getLogger("Discord_Radio_Bot.LinkCop") + class LinkCop(commands.Cog): def __init__(self, bot): self.bot = bot @@ -59,7 +62,7 @@ class LinkCop(commands.Cog): try: if not any(item.id in self.whitelisted_groups for item in ctx.author.roles): if check_message(ctx.content): - print('Msg with links detected in a blocked channel') + LOGGER.info('Msg with links detected in a blocked channel') response_text = self.generate_response(ctx) @@ -68,8 +71,8 @@ class LinkCop(commands.Cog): # Delete the original message await ctx.delete() except AttributeError as err: - print(f"Link Cop Error: '{err}'") - print(f"Bot or other non-user that has not been whitelisted sent a message") + LOGGER.error(f"Link Cop Error: '{err}'\nBot or other non-user that has not " + f"been whitelisted sent a message") await check_and_reply_to_ping(self.bot, ctx) @@ -123,7 +126,7 @@ def check_message(message_text): def get_links(message_text): links = regex_link.findall(message_text) - print(links) + LOGGER.error(links) if len(links) > 0: return links diff --git a/op25Handler.py b/op25Handler.py index 6094ac6..947f62a 100644 --- a/op25Handler.py +++ b/op25Handler.py @@ -1,8 +1,7 @@ +import logging import shutil -import threading import subprocess -import asyncio -import os +import threading import time @@ -21,6 +20,10 @@ class OP25Handler(threading.Thread): self.Stop_OP25 = False + self.Output_Device_Name = None + + self.logger = logging.getLogger("Discord_Radio_Bot.OP25Handler") + def run(self) -> None: while True: if self.Start_OP25: @@ -36,7 +39,8 @@ class OP25Handler(threading.Thread): time.sleep(.5) - def set_op25_parameters(self, _frequency: str = False, _http_enabled: bool = True, _start: bool = False, _stop: bool = False): + def set_op25_parameters(self, _frequency: str = False, _http_enabled: bool = True, _start: bool = False, + _stop: bool = False, _output_device_name: str = None): if _frequency: self.Frequency = _frequency @@ -49,24 +53,27 @@ class OP25Handler(threading.Thread): if _http_enabled: self.HTTP_ENABLED = _http_enabled + if _output_device_name: + self.Output_Device_Name = _output_device_name + def open_op25(self): if self.OP25Proc is not None: self.close_op25() - p25_kwargs = [f"./rx.py", "--args", "rtl", "-N", "LNA:49", "-s", "200000", "-o", "25600", "-U", "-f", - f"{self.Frequency}e6", "-X", "-2"] + p25_kwargs = [f"./rx.py", "--args", "rtl", "-N", "LNA:49", "-s", "200000", "-o", "25600", "-w", "-U", "-O", + f"{self.Output_Device_Name}", "-f", f"{self.Frequency}e6", "-X", "-2"] - print(f"Starting OP25") + self.logger.info(f"Starting OP25") # Change the interpreter's working directory (idr why) if self.HTTP_ENABLED: p25_kwargs.extend(["-l", "http:0.0.0.0:8080"]) - print(f"OP25 Keyword Args: {p25_kwargs}") + self.logger.debug(f"OP25 Keyword Args: {p25_kwargs}") self.OP25Proc = subprocess.Popen(p25_kwargs, executable=self.OP25EXE, shell=False, cwd=self.OP25Dir) def close_op25(self): - print(f"Closing OP25") + self.logger.info(f"Closing OP25") try: self.OP25Proc.kill() @@ -74,11 +81,11 @@ class OP25Handler(threading.Thread): while self.OP25Proc.poll() is None: # Terminate the process every 5 seconds if seconds_waited % 5 == 0: - print("Terminating OP25") + self.logger.debug("Terminating OP25") self.OP25Proc.terminate() time.sleep(1) - print(f"Waited {seconds_waited} seconds") + self.logger.debug(f"Waited {seconds_waited} seconds") seconds_waited += 1 except Exception as e: - print(e) + self.logger.error(e)