Cannot get this Timer To restart

47 Views Asked by At

Im building a bot for fun and to test my knowledge with python. A quick overview of the project:

It takes sound from a game , cross refrences it with a target sound, if target sound is detected do some actions.

i have a timer that runs from the time i press start in gui , when timer reaches zero it should reset back to its orignal duration to start again and take another action. if target sound is detected at some point during that timer, stop and reset timer ,take an action and then restart.

this works , for a while as intended, but then it just stops.

trying the timer without sound doesnt restart the timer at all which is not intended.

basically i need the timer to reach 0, reset, then start again in a loop until manually interrupted

Print statements at key locations confirm the timer gets reset , however the restart is never called , see Code below for 3 python files to handle the project

Main/GUI.py


import ttkbootstrap as tb
from ttkbootstrap.constants import *
import threading
import pyautogui
import time

from AudioProcessing import AudioStreamProcessor
from CountDown import CountdownTimer

# Disable pyautogui failsafe
pyautogui.FAILSAFE = False

# Global variables and initialization
font = "Roboto"
start_timer = None
fishing_timer = None
long_term_timer = None
audio_processor = None
is_started = False
default_duration = 7200  # 2 hours

# Initialize root window
root = tb.Window(themename='yeti')
root.title("The Lazy Fisher")
root.geometry("1024x900")

# GUI Elements (Defined later)
info_relay = None  # Info display textbox
progress_meter = None  # Casting progress bar
vendoring_meter = None  # Vendoring progress bar
threshold_entry = None  # Threshold entry field
timer_entry = None  # Timer entry field

# Function Definitions
def add_message_to_gui(message, color):
    if info_relay:
        info_relay.configure(state=tb.NORMAL)
        info_relay.insert(tb.END, f"{message}\n", color)
        info_relay.yview(tb.END)
        info_relay.configure(state=tb.DISABLED)

def update_threshold_value():
    try:
        threshold_value = float(threshold_entry.get())
        audio_processor.THRESHOLD = threshold_value
        add_message_to_gui(f"Threshold set to {threshold_value}", "white")
    except ValueError:
        add_message_to_gui("Invalid input for threshold!", "red")

def fish_hooked():
    root.after(0, add_message_to_gui("Fish Hooked", "orange"))
    
def start_fish_timer():
    global is_started, fishing_timer, long_term_timer
    if not is_started:
        is_started = True
        if not fishing_timer.is_running():
            fishing_timer.reset()
            fishing_timer.start()
            long_term_timer.reset()
            long_term_timer.start()
        add_message_to_gui("Fishing process started.", "green")
    else:
        add_message_to_gui("Fishing process is already running.", "orange")

def start_timer_update_callback(remaining):
    root.after(0, add_message_to_gui(f"Starting in {remaining}", "orange"))

def start_timer_end_callback():
    global fishing_timer, long_term_timer
    fishing_timer.start()
    long_term_timer.start()
    pyautogui.press('1')  # Automated action to start
    add_message_to_gui("Fishing process started.", "green")

def fishing_timer_update_callback(remaining):
    global is_started
    if is_started:
        root.after(0, add_message_to_gui(f"Recast in {remaining}", "white"))
        progress_meter.configure(amountused=remaining)

def fishing_timer_end_callback():
    try:
        fishing_timer.reset()
        fishing_timer.start()
        add_message_to_gui("Timer reset and started.", "white")
    except Exception as e:
        print(f"Error in fishing_timer_end_callback: {e}")

def long_term_timer_update_callback(remaining):
    global is_started
    if is_started:
        vendoring_meter.configure(amountused=remaining)

def long_term_timer_end_callback():
    pyautogui.press('2')
    time.sleep(4)
    pyautogui.press('f')
    time.sleep(3)
    pyautogui.press('1')
    long_term_timer.reset()
    long_term_timer.start()



def stop_process():
    global is_started
    if is_started:
        is_started = False
        if fishing_timer.is_running():
            fishing_timer.stop()
            fishing_timer.reset()
        if long_term_timer.is_running():
            long_term_timer.stop()
            long_term_timer.reset()
        add_message_to_gui("Fishing process stopped.", "red")
    else:
        add_message_to_gui("Fishing process is not running.", "orange")

def start_audio_processing():
    global audio_thread
    audio_thread = threading.Thread(target=audio_processor.open_stream)
    audio_thread.start()
    add_message_to_gui("Audio processing started.", "green")

def stop_audio_processing():
    global is_started, audio_thread
    if is_started:
        is_started = False
        audio_processor.close_stream()
        if audio_thread.is_alive():
            audio_thread.join()
        add_message_to_gui("Audio processing stopped.", "red")

def update_vend_time():
    try:
        vendorTime = float(timer_entry.get()) * 60
        long_term_timer.update_duration(vendorTime)
        add_message_to_gui(f"Vendoring in {vendorTime/60} minutes", "white")
        vendoring_meter.configure(amounttotal=vendorTime / 60)
    except ValueError:
        add_message_to_gui("Enter your time in minutes only", "red")

# Initialize timers and audio processor
start_timer = CountdownTimer(5, start_timer_update_callback, start_timer_end_callback, None)
fishing_timer = CountdownTimer(5, fishing_timer_update_callback, fishing_timer_end_callback, audio_processor)
long_term_timer = CountdownTimer(default_duration, long_term_timer_update_callback, long_term_timer_end_callback, None)
audio_processor = AudioStreamProcessor(timer=fishing_timer, on_detection_callback=fish_hooked)

# Set timer in the audio processor
audio_processor.set_timer(fishing_timer)

title = tb.Label(root, text="The Lazy Fisher", font=("Lobster", 48))
title.pack(pady=20, padx=50)

# Frame for Entries
entry_frame = tb.Frame(root)
entry_frame.pack(fill="x", padx=100)

#Threshold Label
threshold_label = tb.Label(entry_frame, text="Threshold:            ", font=("Lobster", 20), justify="left")
threshold_label.pack(side="left")
threshold_entry = tb.Entry(entry_frame)
threshold_entry.pack(side="left", expand=True) 
threshold_button = tb.Button(entry_frame, command=update_threshold_value, text="Set", bootstyle="Primary-outline", width=20)
threshold_button.pack(side="left")

#Frame for Timers
timer_frame = tb.Frame(root)
timer_frame.pack(fill="x", pady=10, padx=100)
timer_label = tb.Label(timer_frame, text="Time Until Vend:", font=("Lobster", 20), justify="left")
timer_label.pack(side="left")
timer_entry = tb.Entry(timer_frame, bootstyle="secondary")
timer_entry.pack(side="left", expand=True)
timer_button = tb.Button(timer_frame, text="Set", command=update_vend_time, bootstyle="secondary-outline", width=20)
timer_button.pack(side="right")

#Start/Stop Buttons Frame
state_button_frame = tb.Frame(root)
state_button_frame.pack() 

#Start Button
start_button = tb.Button(state_button_frame, command=start_fish_timer, text="Start", bootstyle="success-outline", width=100)
start_button.pack(pady=5)

#Start Button
stop_button = tb.Button(state_button_frame, command=stop_process, text="Stop", bootstyle="danger-outline", width=100)
stop_button.pack(pady=5)

#Frame for Progress Bars
progress_frame = tb.Frame(root)
progress_frame.pack(pady=10)

#casting frame
casting_frame = tb.Frame(progress_frame)
casting_frame.pack(side="left", padx=20)

progress_label = tb.Label(casting_frame, text="Casting", font=("Lobster", 20), justify="center")
progress_label.pack(pady=10)
progress_meter = tb.Meter(casting_frame, bootstyle="success",amounttotal=fishing_timer.duration, amountused=fishing_timer.remaining ,wedgesize=10, metersize=100 )
progress_meter.pack()

#Vendoring Frame
vendoring_frame = tb.Frame(progress_frame)
vendoring_frame.pack(side="right", padx=20)
vendoring_label = tb.Label(vendoring_frame, text="Vendor in", font=("Lobster", 20), justify="center")
vendoring_label.pack(pady=10)
vendoring_meter = tb.Meter(vendoring_frame, bootstyle="danger",wedgesize=0,amounttotal=default_duration, amountused=long_term_timer.remaining ,metersize=100)
vendoring_meter.pack()


#TextBox Frame
textbox_frame = tb.Frame(root)
textbox_frame.pack(pady=5)

#textbox
info_relay = tb.Text(textbox_frame, width=300, height=300, state=tb.DISABLED)
info_relay.pack(padx=20)

info_relay.tag_config('green', foreground='green')
info_relay.tag_config('orange', foreground='orange')
info_relay.tag_config('red', foreground='red')
info_relay.tag_config('white', foreground='white')

# Start audio processing
start_audio_processing()

# Main loop
try:
    root.mainloop()
except KeyboardInterrupt:
    audio_processor.close_stream()
    root.quit()

*** Custom AudioProcessor ***

from pydub import AudioSegment
import numpy as np
import pyaudio
from scipy.signal import correlate
from time import time
# ... other imports ...


# Audio stream parameters
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
CHUNK = 2048
VIRTUAL_CABLE_INDEX = 1

class AudioStreamProcessor:
    def __init__(self, timer=None, on_detection_callback=None):
        self.p = pyaudio.PyAudio()
        self.stream = None
        self.max_correlation = float(0)
        self.THRESHOLD = float(25)
        self.detected = False
        self.on_detection_callback = on_detection_callback
        self.timer = timer
        self.last_detected_time = time.time()
        self.debounce_period = 1
        # Load the WAV file and prepare the target sound
        wav_path = r"D:\fishing\fish\FishingBobber_ver2_2.wav"
        target_sound = AudioSegment.from_wav(wav_path)

        # Prepare the target sound array
        target_sound_array = np.array(target_sound.get_array_of_samples(), dtype=np.float32)
        if target_sound.channels == 2:
            target_sound_array = target_sound_array.reshape(-1, 2)
            target_sound_array = np.mean(target_sound_array, axis=1)
        self.target_sound_array = target_sound_array / np.max(np.abs(target_sound_array))


    def set_timer(self, timer):
        self.timer = timer

    def callback(self, in_data, frame_count, time_info, status):
        audio_data = np.frombuffer(in_data, dtype=np.int16)
        if CHANNELS == 2:
            audio_data = audio_data.reshape(-1, 2)
            audio_data = audio_data[:, 0]

        if np.any(audio_data):
            audio_data = audio_data / np.max(np.abs(audio_data))
            correlation = correlate(audio_data, self.target_sound_array, mode="valid")
            self.max_correlation = np.max(correlation)
            
        current_time = time()
        if self.max_correlation > self.THRESHOLD:
            # Check if 2 seconds have passed since the last detection
            if current_time - self.last_detected_time > self.debounce_period:
                self.last_detected_time = current_time  # Update the last detection time
                self.detected = True  # Set detected flag to True
                if self.timer:
                    self.timer.handle_detection()
                if self.on_detection_callback:
                    self.on_detection_callback()
            else:
                # If 2 seconds haven't passed, ignore this detection (debounce)
                self.detected = False
        else:
            # If the correlation is not greater than the threshold
            self.detected = False
                    
        return (in_data, pyaudio.paContinue)

    def open_stream(self):
        if self.stream is None:
            self.stream = self.p.open(rate=RATE, channels=CHANNELS, format=FORMAT, 
                                      frames_per_buffer=CHUNK, input=True, 
                                      input_device_index=VIRTUAL_CABLE_INDEX, 
                                      stream_callback=self.callback)
        self.stream.start_stream()

    def close_stream(self):
        if self.stream is not None:
            self.stream.stop_stream()
            self.stream.close()
            self.stream = None
        self.p.terminate()


audio_processor = AudioStreamProcessor()

*** Custom CountDownTimer ***

import time
from threading import Thread
import pyautogui

class CountdownTimer:
    def __init__(self, duration, update_callback, end_callback, audio_processor):
        self.duration = duration
        self.remaining = duration
        self.update_callback = update_callback
        self.end_callback = end_callback
        self.audio_processor = audio_processor
        self._timer_thread = None
        self._stop_flag = False
        self.last_start_time = 0  
        self.minimum_restart_interval = 2 

    def start(self):
        if self._timer_thread is None:
            self._stop_flag = False
            self.last_start_time = time.time()
            self._timer_thread = Thread(target=self._run)
            self._timer_thread.start()

    def update_duration(self, new_duration):
        self.duration = new_duration
        self.remaining = new_duration

    def stop(self):
        self._stop_flag = True


    def reset(self):
        self.remaining = self.duration
    
    def is_running(self):
        return self._timer_thread is not None and self._timer_thread.is_alive()

    def _run(self):
        while self.remaining > 0 and not self._stop_flag:
            time.sleep(1)
            self.remaining -= 1
            
            # Call update_callback if it's set
            if self.update_callback is not None:
                self.update_callback(self.remaining)


            # Handle detection if audio_processor is set and detection is found
            if self.audio_processor and self.audio_processor.detected:
                if hasattr(self, 'handle_detection'):
                    self.handle_detection()
                break

        if not self._stop_flag:
            if self.end_callback:
                self.end_callback()
            self.last_start_time = time.time()  # Update the start time for the new cycle
        self._timer_thread = None

    def handle_detection(self):
        pyautogui.press('f')
        time.sleep(2)
        self.reset()
        self.start()
        pyautogui.press('1')

i have spent the last 5 hours trying to get this to work and i just cannot get my head around why its not working as intended.Iv gone over the callbacks again and again trying try except blocks , if statements and just straight up code blocks with no luck. Any insight would be greatly appreciated , please keep in mind i am still learning as i go. Will gladly take any knowledge to be shared to improve

0

There are 0 best solutions below