diff --git a/.gitignore b/.gitignore index 8684325..a9c4648 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea/ /Releases/ +/Old/ /__pycache__/ */__pycache__/ /venv/ diff --git a/BotResources.py b/BotResources.py index 0d05b7e..4052be6 100644 --- a/BotResources.py +++ b/BotResources.py @@ -2,6 +2,8 @@ import sound import configparser from os.path import exists +PDB_ACCEPTABLE_HANDLERS = ['gqrx', 'op25'] + def check_if_config_exists(): if exists('./config.ini'): @@ -167,7 +169,7 @@ def get_handler(): handler = None while not handler: handler = str(input(f"Please enter the name of the handler you would like to use:\t")) - if handler == "gqrx": + if handler in PDB_ACCEPTABLE_HANDLERS: return handler elif handler == '': return handler diff --git a/bot.py b/bot.py index bf1f148..c20698a 100644 --- a/bot.py +++ b/bot.py @@ -4,7 +4,6 @@ import discord import sound import configparser from discord.ext import commands -from gqrxHandler import GQRXHandler # Init class for bot @@ -43,9 +42,8 @@ class Bot(commands.Bot): self.system_os_type = None self.sdr_started = False - if self.Handler == "gqrx": - print("Starting gqrx handler") - self.GQRXHandler = GQRXHandler() + # Check the handler being used + self.check_handler() # Set linux or windows self.check_os_type() @@ -56,6 +54,18 @@ class Bot(commands.Bot): # Check the ./modules folder for any modules (cog.py) self.check_for_modules() + # Check the handler being used during init + def check_handler(self): + if self.Handler == "gqrx": + print("Starting GQRX handler") + from gqrxHandler import GQRXHandler + self.GQRXHandler = GQRXHandler() + + elif self.Handler == 'op25': + print("Starting OP25 handler") + from op25Handler import OP25Handler + self.OP25Handler = OP25Handler() + # Start the bot def start_bot(self): self.run(self.BOT_TOKEN) @@ -88,10 +98,13 @@ class Bot(commands.Bot): voice_connection = await channel.connect() # Create an audio stream from selected device - self.streamHandler = sound.PCMStream(voice_connection) + self.streamHandler = sound.PCMStream() # Ensure the selected device is available and start the audio stream self.streamHandler.change_device(self.DEVICE_ID) + # Play the stream + voice_connection.play(discord.PCMAudio(self.streamHandler)) + # Start the SDR and begin playing to the audio stream self.start_sdr() @@ -123,14 +136,25 @@ class Bot(commands.Bot): # 'Unlock' the bot self.Bot_Connected = False - if self.Handler == 'gqrx': - @self.command(name='chfreq', help="Use this command to change the frequency the bot is listening to. " - "\nExample command: '@ chfreq wfm 104700000\n" - "Example command: '@ chfreq fm 154785000", + # Add commands for GQRX and OP25 + if self.Handler == 'gqrx' or self.Handler == 'op25': + @self.command(name='chfreq', help="Use this command to change the frequency the bot is listening to.\n" + "Example GQRX command:\n" + "\tTune to 104.7Mhz Wideband FM (Radio) - '@ chfreq wfm 104700000\n" + "\tTune to 155.505Mhz Narrowband FM (Radio) - '@ chfreq fm 155505000\n" + "Example OP25 command:\n" + "\tTune to 155.310Mhz, decode using P25 - '@ chfreq p25 155.310", brief="Changes radio frequency") async def chfreq(ctx, mode: str, freq: str, member: discord.Member = None): # Possible band-types that can be used - possible_modes = ['wfm', 'fm'] + possible_modes = [] + + if self.Handler == 'gqrx': + possible_modes = ['wfm', 'fm'] + + elif self.Handler == 'op25': + possible_modes = ['d', 'p25'] + member = member or ctx.author.display_name # Check to make sure the frequency input matches the syntax needed @@ -140,8 +164,8 @@ class Bot(commands.Bot): if mode in possible_modes: self.mode = mode - await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the mode to {str(self.mode).upper()} and frequency to" - f" {self.freq}") + await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the mode to " + f"{str(self.mode).upper()} and frequency to {self.freq}") # Reset the profile name since we have made a change to the freq self.profile_name = None @@ -151,22 +175,29 @@ class Bot(commands.Bot): self.start_sdr() await self.set_activity() else: - await ctx.send(f"{str(member).capitalize()}, {mode} is not valid. You may only enter 'fm' or 'wbfm'") + await ctx.send(f"{str(member).capitalize()}, {mode} is not valid." + f" You may only enter {possible_modes}") else: - await ctx.send(f"{str(member).capitalize()}, {freq} is not valid. please refer to the help page '@ help chfreq'") + await ctx.send(f"{str(member).capitalize()}, {freq} is not valid. " + f"Please refer to the help page '@ help chfreq'") - @self.command(name='chsquelch', help="Use this command to change the squelch for the frequency" - "the bot is listening to", - brief="Changes radio squelch") - async def chsquelch(ctx, squelch: float, member: discord.Member = None): - member = member or ctx.author.display_name + # GQRX Specific commands + if self.Handler == 'gqrx': + @self.command(name='chsquelch', help="Use this command to change the squelch for the frequency " + "the bot is listening to\n" + "Example Commands:\n" + "\tNo Squelch\t'@ chsquelch 150'\n" + "\tFully Squelched\t'@ chsquelch 0'", + brief="Changes radio squelch") + async def chsquelch(ctx, squelch: float, member: discord.Member = None): + member = member or ctx.author.display_name - self.squelch = squelch - await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the squelch to {self.squelch}") + self.squelch = squelch + await ctx.send(f"Ok {str(member).capitalize()}, I'm changing the squelch to {self.squelch}") - # If the SDR is started, restart it with the updates - if self.sdr_started: - self.start_sdr() + # If the SDR is started, restart it with the updates + if self.sdr_started: + self.start_sdr() # Hidden admin commands @self.command(name='saveprofile', hidden=True) @@ -202,6 +233,7 @@ class Bot(commands.Bot): async def _stopsdr(ctx, member: discord.Member = None): self.stop_sdr() + # Load the proper OPUS library for the device being used def load_opus(self): # Check the system type and load the correct library if self.system_os_type == 'Linux_32': @@ -260,7 +292,7 @@ class Bot(commands.Bot): # Check to see if there is only one frequency def start_sdr(self): - if self.Handler == 'gqrx': + if self.Handler in ['gqrx', 'op25']: if type(self.freq) == str: # Single freq sent # Stop the SDR if it is running @@ -269,8 +301,13 @@ class Bot(commands.Bot): # Start the radio print(f"Starting freq: {self.freq}") - # Set the settings in GQRX - self.GQRXHandler.set_all_settings(self.mode, self.squelch, 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) + self.OP25Handler.start() # Set the started variable for later checks self.sdr_started = True @@ -279,13 +316,16 @@ class Bot(commands.Bot): def stop_sdr(self): if self.sdr_started: # Wait for the running processes to close + if self.Handler == 'op25': + self.OP25Handler.close_op25() + #self.OP25Handler.join() # Need a way to 'close' GQRX self.sdr_started = False # Set the activity of the bot async def set_activity(self, connected=True): if connected: - if self.Handler == 'gqrx': + if self.Handler in ['gqrx', 'op25']: if self.profile_name is None: await self.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"{self.freq[:-1]}" @@ -345,3 +385,5 @@ class Bot(commands.Bot): return False else: return False + + diff --git a/main.py b/main.py index e1a67de..a49a723 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,9 @@ from BotResources import check_if_config_exists, write_config_file, read_config_ # Greada #token = 'NzU2MzI3MjcxNTk3NDczODYz.X2QOqQ.LVLj2b-RXQzPmhNuBC1eGFMcYls' +# Brent +#token = OTQzNzQyMDQwMjU1MTE1MzA0.Yg3eRA.ZxEbRr55xahjfaUmPY8pmS-RHTY + #name = "VoiceMeeter Output" diff --git a/op25Handler.py b/op25Handler.py new file mode 100644 index 0000000..3926ec0 --- /dev/null +++ b/op25Handler.py @@ -0,0 +1,53 @@ +import threading +import subprocess +import asyncio +import os + + +class OP25Handler: #(threading.Thread): + def __init__(self): + super().__init__() + self.OP25Dir: str = "/home/pi/op25/op25/gr-op25_repeater/apps" + self.OP25Proc = None + + self.Frequency = None + + def start(self) -> None: + self.open_op25() + + def set_op25_parameters(self, _frequency): + self.Frequency = _frequency + + def open_op25(self): + if self.OP25Proc is not None: + self.close_op25() + + print(f"Starting OP25") + + print(os.getcwd()) + + os.chdir(self.OP25Dir) + + print(os.getcwd()) + + self.OP25Proc = subprocess.Popen([f"./rx.py", "--args", "rtl", "-N", "LNA:49", "-s", + "200000", "-o", "25600", "-U", "-f", f"{self.Frequency}e6", "-X", "-2", + "-l" "http:0.0.0.0:8080"]) + + def close_op25(self): + print(f"Closing OP25") + try: + self.OP25Proc.kill() + + seconds_waited = 0 + while self.OP25Proc.poll() is None: + # Terminate the process every 5 seconds + if seconds_waited % 5 == 0: + print("Terminating OP25") + self.OP25Proc.terminate() + asyncio.sleep(1000) + print(f"Waited {seconds_waited} seconds") + seconds_waited += 1 + + except Exception as e: + print(e) diff --git a/sound.py b/sound.py index bf0297f..be7cf28 100644 --- a/sound.py +++ b/sound.py @@ -1,6 +1,5 @@ import sounddevice as sd from pprint import pformat -from NoiseGate import NoiseGate DEFAULT = 0 sd.default.channels = 2 @@ -8,14 +7,11 @@ sd.default.dtype = "int16" sd.default.latency = "low" sd.default.samplerate = 48000 -noisegate_obj = NoiseGate() - class PCMStream: globals() - def __init__(self, voice_connection): + def __init__(self): self.stream = None - self.voice_connection = voice_connection def read(self, num_bytes): # frame is 4 bytes @@ -29,24 +25,14 @@ class PCMStream: self.clean_up() self.stream = sd.RawInputStream(device=device_ID) - noisegate_obj.init_stream(device_ID, self.voice_connection, self) self.stream.start() - noisegate_obj.start() def clean_up(self): - global noisegate_obj if self.stream is not None: self.stream.stop() self.stream.close() - if noisegate_obj.NG_Started.is_set(): - print("Closing the noisegate") - noisegate_obj.stop_NG.set() - noisegate_obj.join() - noisegate_obj = NoiseGate() - print("Started the noisegate") - class DeviceNotFoundError(Exception): def __init__(self):