|
|
@@ -8,17 +8,13 @@ from tqdm import tqdm
|
|
|
from modules.connection import connection_manager
|
|
|
from modules.core.state import state
|
|
|
from math import pi
|
|
|
+from modules.led.led_controller import effect_playing, effect_idle
|
|
|
|
|
|
# Configure logging
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Global state
|
|
|
THETA_RHO_DIR = './patterns'
|
|
|
-CLEAR_PATTERNS = {
|
|
|
- "clear_from_in": "./patterns/clear_from_in.thr",
|
|
|
- "clear_from_out": "./patterns/clear_from_out.thr",
|
|
|
- "clear_sideway": "./patterns/clear_sideway.thr"
|
|
|
-}
|
|
|
os.makedirs(THETA_RHO_DIR, exist_ok=True)
|
|
|
|
|
|
def list_theta_rho_files():
|
|
|
@@ -60,58 +56,56 @@ def parse_theta_rho_file(file_path):
|
|
|
return coordinates
|
|
|
|
|
|
def get_clear_pattern_file(clear_pattern_mode, path=None):
|
|
|
- """Return a .thr file path based on pattern_name."""
|
|
|
+ """Return a .thr file path based on pattern_name and table type."""
|
|
|
if not clear_pattern_mode or clear_pattern_mode == 'none':
|
|
|
return
|
|
|
- logger.info("Clear pattern mode: " + clear_pattern_mode)
|
|
|
+
|
|
|
+ # Define patterns for each table type
|
|
|
+ clear_patterns = {
|
|
|
+ 'dune_weaver': {
|
|
|
+ 'clear_from_out': './patterns/clear_from_out.thr',
|
|
|
+ 'clear_from_in': './patterns/clear_from_in.thr',
|
|
|
+ 'clear_sideway': './patterns/clear_sideway.thr'
|
|
|
+ },
|
|
|
+ 'dune_weaver_mini': {
|
|
|
+ 'clear_from_out': './patterns/clear_from_out_mini.thr',
|
|
|
+ 'clear_from_in': './patterns/clear_from_in_mini.thr',
|
|
|
+ 'clear_sideway': './patterns/clear_sideway_mini.thr'
|
|
|
+ },
|
|
|
+ 'dune_weaver_pro': {
|
|
|
+ 'clear_from_out': './patterns/clear_from_out_pro.thr',
|
|
|
+ 'clear_from_in': './patterns/clear_from_in_pro.thr',
|
|
|
+ 'clear_sideway': './patterns/clear_sideway_pro.thr'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ # Get patterns for current table type, fallback to standard patterns if type not found
|
|
|
+ table_patterns = clear_patterns.get(state.table_type, clear_patterns['dune_weaver'])
|
|
|
+
|
|
|
+ logger.debug(f"Clear pattern mode: {clear_pattern_mode} for table type: {state.table_type}")
|
|
|
+
|
|
|
if clear_pattern_mode == "random":
|
|
|
- return random.choice(list(CLEAR_PATTERNS.values()))
|
|
|
+ return random.choice(list(table_patterns.values()))
|
|
|
|
|
|
if clear_pattern_mode == 'adaptive':
|
|
|
- _, first_rho = parse_theta_rho_file(path)[0]
|
|
|
+ if not path:
|
|
|
+ logger.warning("No path provided for adaptive clear pattern")
|
|
|
+ return random.choice(list(table_patterns.values()))
|
|
|
+
|
|
|
+ coordinates = parse_theta_rho_file(path)
|
|
|
+ if not coordinates:
|
|
|
+ logger.warning("No valid coordinates found in file for adaptive clear pattern")
|
|
|
+ return random.choice(list(table_patterns.values()))
|
|
|
+
|
|
|
+ first_rho = coordinates[0][1]
|
|
|
if first_rho < 0.5:
|
|
|
- return CLEAR_PATTERNS['clear_from_out']
|
|
|
+ return table_patterns['clear_from_out']
|
|
|
else:
|
|
|
- return random.choice([CLEAR_PATTERNS['clear_from_in'], CLEAR_PATTERNS['clear_sideway']])
|
|
|
- else:
|
|
|
- return CLEAR_PATTERNS[clear_pattern_mode]
|
|
|
-
|
|
|
-def schedule_checker(schedule_hours):
|
|
|
- """Pauses/resumes execution based on a given time range."""
|
|
|
- if not schedule_hours:
|
|
|
- return
|
|
|
-
|
|
|
- start_time, end_time = schedule_hours
|
|
|
- now = datetime.now().time()
|
|
|
-
|
|
|
- if start_time <= now < end_time:
|
|
|
- if state.pause_requested:
|
|
|
- logger.info("Starting execution: Within schedule")
|
|
|
- connection_manager.update_machine_position()
|
|
|
- state.pause_requested = False
|
|
|
- with state.pause_condition:
|
|
|
- state.pause_condition.notify_all()
|
|
|
+ return random.choice([table_patterns['clear_from_in'], table_patterns['clear_sideway']])
|
|
|
else:
|
|
|
- if not state.pause_requested:
|
|
|
- logger.info("Pausing execution: Outside schedule")
|
|
|
- state.pause_requested = True
|
|
|
- connection_manager.update_machine_position()
|
|
|
- threading.Thread(target=wait_for_start_time, args=(schedule_hours,), daemon=True).start()
|
|
|
-
|
|
|
-def wait_for_start_time(schedule_hours):
|
|
|
- """Keep checking every 30 seconds if the time is within the schedule to resume execution."""
|
|
|
- start_time, end_time = schedule_hours
|
|
|
-
|
|
|
- while state.pause_requested:
|
|
|
- now = datetime.now().time()
|
|
|
- if start_time <= now < end_time:
|
|
|
- logger.info("Resuming execution: Within schedule")
|
|
|
- state.pause_requested = False
|
|
|
- with state.pause_condition:
|
|
|
- state.pause_condition.notify_all()
|
|
|
- break
|
|
|
- else:
|
|
|
- time.sleep(30)
|
|
|
+ if clear_pattern_mode not in table_patterns:
|
|
|
+ return False
|
|
|
+ return table_patterns[clear_pattern_mode]
|
|
|
|
|
|
|
|
|
def move_polar(theta, rho):
|
|
|
@@ -193,7 +187,7 @@ def set_speed(new_speed):
|
|
|
state.speed = new_speed
|
|
|
logger.info(f'Set new state.speed {new_speed}')
|
|
|
|
|
|
-def run_theta_rho_file(file_path, schedule_hours=None):
|
|
|
+def run_theta_rho_file(file_path):
|
|
|
"""Run a theta-rho file by sending data in optimized batches with tqdm ETA tracking."""
|
|
|
|
|
|
# Check if connection is still valid, if not, restart
|
|
|
@@ -226,6 +220,11 @@ def run_theta_rho_file(file_path, schedule_hours=None):
|
|
|
logger.info(f"Starting pattern execution: {file_path}")
|
|
|
logger.info(f"t: {state.current_theta}, r: {state.current_rho}")
|
|
|
reset_theta()
|
|
|
+
|
|
|
+ if state.led_controller:
|
|
|
+ effect_playing(state.led_controller)
|
|
|
+
|
|
|
+
|
|
|
with tqdm(
|
|
|
total=total_coordinates,
|
|
|
unit="coords",
|
|
|
@@ -237,15 +236,28 @@ def run_theta_rho_file(file_path, schedule_hours=None):
|
|
|
for i, coordinate in enumerate(coordinates):
|
|
|
theta, rho = coordinate
|
|
|
if state.stop_requested:
|
|
|
- logger.info("Execution stopped by user after completing the current batch")
|
|
|
+ logger.info("Execution stopped by user")
|
|
|
+ if state.led_controller:
|
|
|
+ effect_idle(state.led_controller)
|
|
|
+ break
|
|
|
+
|
|
|
+ if state.skip_requested:
|
|
|
+ logger.info("Skipping pattern...")
|
|
|
+ connection_manager.check_idle()
|
|
|
+ if state.led_controller:
|
|
|
+ effect_idle(state.led_controller)
|
|
|
break
|
|
|
|
|
|
- with state.pause_condition:
|
|
|
- while state.pause_requested:
|
|
|
- logger.info("Execution paused...")
|
|
|
- state.pause_condition.wait()
|
|
|
+ # Wait for resume if paused
|
|
|
+ if state.pause_requested:
|
|
|
+ logger.info("Execution paused...")
|
|
|
+ if state.led_controller:
|
|
|
+ effect_idle(state.led_controller)
|
|
|
+ pause_event.wait()
|
|
|
+ logger.info("Execution resumed...")
|
|
|
+ if state.led_controller:
|
|
|
+ effect_playing(state.led_controller)
|
|
|
|
|
|
- schedule_checker(schedule_hours)
|
|
|
move_polar(theta, rho)
|
|
|
|
|
|
if i != 0:
|
|
|
@@ -260,71 +272,95 @@ def run_theta_rho_file(file_path, schedule_hours=None):
|
|
|
state.execution_progress = None
|
|
|
logger.info("Pattern execution completed")
|
|
|
|
|
|
-def run_theta_rho_files(file_paths, pause_time=0, clear_pattern=None, run_mode="single", shuffle=False, schedule_hours=None):
|
|
|
+def run_theta_rho_files(file_paths, pause_time=0, clear_pattern=None, run_mode="single", shuffle=False):
|
|
|
"""Run multiple .thr files in sequence with options."""
|
|
|
state.stop_requested = False
|
|
|
|
|
|
# Set initial playlist state
|
|
|
state.playlist_mode = run_mode
|
|
|
state.current_playlist_index = 0
|
|
|
-
|
|
|
- if shuffle:
|
|
|
- random.shuffle(file_paths)
|
|
|
- logger.info("Playlist shuffled")
|
|
|
|
|
|
- while True:
|
|
|
- for idx, path in enumerate(file_paths):
|
|
|
- logger.info(f"Upcoming pattern: {path}")
|
|
|
- state.current_playlist_index = idx
|
|
|
- schedule_checker(schedule_hours)
|
|
|
- if state.stop_requested:
|
|
|
- logger.info("Execution stopped before starting next pattern")
|
|
|
- state.current_playlist = None
|
|
|
- state.current_playlist_index = None
|
|
|
- state.playlist_mode = None
|
|
|
- return
|
|
|
+ try:
|
|
|
+ while True:
|
|
|
+ # Construct the complete pattern sequence
|
|
|
+ pattern_sequence = []
|
|
|
+ for path in file_paths:
|
|
|
+ # Add clear pattern if specified
|
|
|
+ if clear_pattern and clear_pattern != 'none':
|
|
|
+ clear_file_path = get_clear_pattern_file(clear_pattern, path)
|
|
|
+ if clear_file_path:
|
|
|
+ pattern_sequence.append(clear_file_path)
|
|
|
+
|
|
|
+ # Add main pattern
|
|
|
+ pattern_sequence.append(path)
|
|
|
|
|
|
- if clear_pattern:
|
|
|
+ # Shuffle if requested
|
|
|
+ if shuffle:
|
|
|
+ # Get pairs of patterns (clear + main) to keep them together
|
|
|
+ pairs = [pattern_sequence[i:i+2] for i in range(0, len(pattern_sequence), 2)]
|
|
|
+ random.shuffle(pairs)
|
|
|
+ # Flatten the pairs back into a single list
|
|
|
+ pattern_sequence = [pattern for pair in pairs for pattern in pair]
|
|
|
+ logger.info("Playlist shuffled")
|
|
|
+
|
|
|
+ # Set the playlist to the first pattern
|
|
|
+ state.current_playlist = pattern_sequence
|
|
|
+ # Execute the pattern sequence
|
|
|
+ for idx, file_path in enumerate(pattern_sequence):
|
|
|
+ state.current_playlist_index = idx
|
|
|
if state.stop_requested:
|
|
|
- logger.info("Execution stopped before running the next clear pattern")
|
|
|
- state.current_playlist = None
|
|
|
- state.current_playlist_index = None
|
|
|
- state.playlist_mode = None
|
|
|
+ logger.info("Execution stopped")
|
|
|
return
|
|
|
|
|
|
- clear_file_path = get_clear_pattern_file(clear_pattern, path)
|
|
|
- logger.info(f"Running clear pattern: {clear_file_path}")
|
|
|
- run_theta_rho_file(clear_file_path, schedule_hours)
|
|
|
-
|
|
|
- if not state.stop_requested:
|
|
|
+ # Update state for main patterns only
|
|
|
+ logger.info(f"Running pattern {file_path}")
|
|
|
|
|
|
- logger.info(f"Running pattern {idx + 1} of {len(file_paths)}: {path}")
|
|
|
- run_theta_rho_file(path, schedule_hours)
|
|
|
+ # Execute the pattern
|
|
|
+ run_theta_rho_file(file_path)
|
|
|
|
|
|
- if idx < len(file_paths) - 1:
|
|
|
- if state.stop_requested:
|
|
|
- logger.info("Execution stopped before running the next clear pattern")
|
|
|
- return
|
|
|
- if pause_time > 0:
|
|
|
+ # Handle pause between patterns
|
|
|
+ if idx < len(pattern_sequence) - 1 and not state.stop_requested and pause_time > 0 and not state.skip_requested:
|
|
|
logger.info(f"Pausing for {pause_time} seconds")
|
|
|
+ pause_start = time.time()
|
|
|
+ while time.time() - pause_start < pause_time:
|
|
|
+ if state.skip_requested:
|
|
|
+ logger.info("Pause interrupted by stop/skip request")
|
|
|
+ break
|
|
|
+ time.sleep(1)
|
|
|
+
|
|
|
+ state.skip_requested = False
|
|
|
+
|
|
|
+ if run_mode == "indefinite":
|
|
|
+ logger.info("Playlist completed. Restarting as per 'indefinite' run mode")
|
|
|
+ if pause_time > 0:
|
|
|
+ logger.debug(f"Pausing for {pause_time} seconds before restarting")
|
|
|
time.sleep(pause_time)
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ logger.info("Playlist completed")
|
|
|
+ break
|
|
|
|
|
|
- if run_mode == "indefinite":
|
|
|
- logger.info("Playlist completed. Restarting as per 'indefinite' run mode")
|
|
|
- if pause_time > 0:
|
|
|
- logger.debug(f"Pausing for {pause_time} seconds before restarting")
|
|
|
- time.sleep(pause_time)
|
|
|
- if shuffle:
|
|
|
- random.shuffle(file_paths)
|
|
|
- logger.info("Playlist reshuffled for the next loop")
|
|
|
- continue
|
|
|
- else:
|
|
|
- logger.info("Playlist completed")
|
|
|
- state.current_playlist = None
|
|
|
- state.current_playlist_index = None
|
|
|
- state.playlist_mode = None
|
|
|
- break
|
|
|
- logger.info("All requested patterns completed (or stopped)")
|
|
|
+ finally:
|
|
|
+ # Clean up progress update task
|
|
|
+ # if progress_update_task:
|
|
|
+ # progress_update_task.cancel()
|
|
|
+ # try:
|
|
|
+ # await progress_update_task
|
|
|
+ # except asyncio.CancelledError:
|
|
|
+ # pass
|
|
|
+ # progress_update_task = None
|
|
|
+
|
|
|
+ # Clear all state variables
|
|
|
+ state.current_playing_file = None
|
|
|
+ state.execution_progress = None
|
|
|
+ state.current_playlist = None
|
|
|
+ state.current_playlist_index = None
|
|
|
+ state.playlist_mode = None
|
|
|
+
|
|
|
+ if state.led_controller:
|
|
|
+ effect_idle(state.led_controller)
|
|
|
+
|
|
|
+ logger.info("All requested patterns completed (or stopped) and state cleared")
|
|
|
|
|
|
def stop_actions(clear_playlist = True):
|
|
|
"""Stop all current actions."""
|