Fabio De Simone пре 11 месеци
родитељ
комит
882c5bfe7f

+ 25 - 12
dune_weaver_flask/modules/core/pattern_manager.py

@@ -146,14 +146,15 @@ def interpolate_path(theta, rho):
     
 def pause_execution():
     logger.info("Pausing pattern execution")
-    pattern_manager.pause_requested = True
+    with state.pause_condition:
+        state.pause_requested = True
     return True
 
 def resume_execution():
     logger.info("Resuming pattern execution")
-    with pattern_manager.pause_condition:
-        pattern_manager.pause_requested = False
-        pattern_manager.pause_condition.notify_all()
+    with state.pause_condition:
+        state.pause_requested = False
+        state.pause_condition.notify_all()
     return True
     
 def reset_theta():
@@ -217,28 +218,34 @@ def run_theta_rho_file(file_path, schedule_hours=None):
 
 def run_theta_rho_files(file_paths, pause_time=0, clear_pattern=None, run_mode="single", shuffle=False, schedule_hours=None):
     """Run multiple .thr files in sequence with options."""
-    global current_playing_index, current_playlist
     state.stop_requested = False
     
     if shuffle:
         random.shuffle(file_paths)
         logger.info("Playlist shuffled")
 
-    current_playlist = file_paths
+    state.current_playlist = file_paths
+    state.playlist_mode = run_mode
 
     while True:
         for idx, path in enumerate(file_paths):
             logger.info(f"Upcoming pattern: {path}")
             logger.info(idx)
-            current_playing_index = idx
+            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
 
             if clear_pattern:
                 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
                     return
 
                 clear_file_path = get_clear_pattern_file(clear_pattern, path)
@@ -268,20 +275,26 @@ def run_theta_rho_files(file_paths, pause_time=0, clear_pattern=None, run_mode="
             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)")
 
 def stop_actions():
-    """Stop all current pattern execution."""
+    """Stop all current actions."""
+
     with state.pause_condition:
         state.pause_requested = False
         state.stop_requested = True
-        current_playing_index = None
-        current_playlist = None
-        state.is_clearing = False
         state.current_playing_file = None
+        state.current_playlist = None
+        state.current_playlist_index = None
         state.execution_progress = None
-    serial_manager.update_machine_position()
+        serial_manager.update_machine_position()
+        state.is_clearing = False
+        state.playlist_mode = None
+        state.pause_condition.notify_all()
 
 def get_status():
     """Get the current execution status."""

+ 12 - 2
dune_weaver_flask/modules/core/state.py

@@ -19,9 +19,13 @@ class AppState:
         # Machine position variables
         self.machine_x = 0.0
         self.machine_y = 0.0
-        self.STATE_FILE = "state.json"
-
         
+        # Playlist state variables
+        self.current_playlist = None
+        self.current_playlist_index = None
+        self.playlist_mode = None  # single, loop, etc.
+        
+        self.STATE_FILE = "state.json"
         self.load()
 
     def to_dict(self):
@@ -37,6 +41,9 @@ class AppState:
             "speed": self.speed,
             "machine_x": self.machine_x,
             "machine_y": self.machine_y,
+            "current_playlist": self.current_playlist,
+            "current_playlist_index": self.current_playlist_index,
+            "playlist_mode": self.playlist_mode
         }
 
     def from_dict(self, data):
@@ -51,6 +58,9 @@ class AppState:
         self.speed = data.get("speed", 250)
         self.machine_x = data.get("machine_x", 0.0)
         self.machine_y = data.get("machine_y", 0.0)
+        self.current_playlist = data.get("current_playlist")
+        self.current_playlist_index = data.get("current_playlist_index")
+        self.playlist_mode = data.get("playlist_mode")
 
     def save(self):
         """Save the current state to a JSON file."""

+ 100 - 27
dune_weaver_flask/modules/mqtt/handler.py

@@ -11,7 +11,7 @@ from .base import BaseMQTTHandler
 from dune_weaver_flask.modules.core.state import state
 from dune_weaver_flask.modules.core.pattern_manager import list_theta_rho_files
 from dune_weaver_flask.modules.core.playlist_manager import list_all_playlists
-from dune_weaver_flask.modules.serial.serial_manager import is_connected
+from dune_weaver_flask.modules.serial.serial_manager import is_connected, get_port
 
 logger = logging.getLogger(__name__)
 
@@ -38,7 +38,7 @@ class MQTTHandler(BaseMQTTHandler):
 
         # Home Assistant MQTT Discovery settings
         self.discovery_prefix = os.getenv('MQTT_DISCOVERY_PREFIX', 'homeassistant')
-        self.device_name = os.getenv('HA_DEVICE_NAME', 'Sand Table')
+        self.device_name = os.getenv('HA_DEVICE_NAME', 'Dune Weaver')
         self.device_id = os.getenv('HA_DEVICE_ID', 'dune_weaver')
         
         # Additional topics for state
@@ -171,20 +171,57 @@ class MQTTHandler(BaseMQTTHandler):
             self.client.publish(self.running_state_topic, "ON" if is_running else "OFF", retain=True)
         
         if current_file is not None:
-            self.current_file = current_file
-            self.client.publish(self.current_file_topic, current_file, retain=True)
-            self.client.publish(f"{self.pattern_select_topic}/state", current_file, retain=True)
+            if current_file:  # Only publish if there's actually a file
+                # Extract just the filename without path and normalize it 
+                if current_file.startswith('./patterns/'):
+                    file_name = current_file[len('./patterns/'):]
+                else:
+                    file_name = current_file.split("/")[-1].split("\\")[-1]
+                
+                self.current_file = file_name
+                # Update both the current file topic and the pattern select state
+                self.client.publish(self.current_file_topic, file_name, retain=True)
+                self.client.publish(f"{self.pattern_select_topic}/state", file_name, retain=True)
+            else:
+                # Clear both states when no file is playing
+                self.client.publish(self.current_file_topic, "", retain=True)
+                self.client.publish(f"{self.pattern_select_topic}/state", "", retain=True)
 
-        if patterns is not None and set(patterns) != set(self.patterns):
-            self.patterns = patterns
-            self.setup_ha_discovery()
+        if patterns is not None:
+            # Only proceed if patterns have actually changed
+            if set(patterns) != set(self.patterns):
+                self.patterns = patterns
+                # Republish discovery config with updated pattern options
+                self.setup_ha_discovery()
         
         if serial is not None:
-            self.serial_state = serial
-            self.client.publish(self.serial_state_topic, serial, retain=True)
-
-        # Update speed state
-        self.client.publish(f"{self.speed_topic}/state", state.speed, retain=True)
+            # Format serial state as "connected to <port>" or "disconnected"
+            if "connected" in serial.lower():
+                port = serial.split(" ")[-1]  # Extract port from status message
+                formatted_state = f"connected to {port}"
+            else:
+                formatted_state = "disconnected"
+            
+            self.serial_state = formatted_state
+            self.client.publish(self.serial_state_topic, formatted_state, retain=True)
+        
+        if playlist is not None:
+            # Update playlist list if needed
+            if playlist.get('all_playlists'):
+                self.playlists = playlist['all_playlists']
+                self.setup_ha_discovery()  # Republish discovery to update playlist options
+            
+            # Publish playlist active state
+            self.client.publish(f"{self.device_id}/state/playlist", json.dumps({
+                "active": bool(playlist.get('current_playlist')),
+            }), retain=True)
+            
+            # Update playlist select state if a playlist is active
+            if playlist.get('current_playlist'):
+                current_playlist_name = playlist['current_playlist'][0]  # Use first file as playlist name
+                self.client.publish(f"{self.playlist_select_topic}/state", current_playlist_name, retain=True)
+            else:
+                self.client.publish(f"{self.playlist_select_topic}/state", "", retain=True)
 
     def on_connect(self, client, userdata, flags, rc):
         """Callback when connected to MQTT broker."""
@@ -238,11 +275,12 @@ class MQTTHandler(BaseMQTTHandler):
         while self.running:
             try:
                 # Create status message
+                is_running = bool(state.current_playing_file) and not state.stop_requested
                 status = {
-                    "status": "running" if not state.stop_requested else "idle",
+                    "status": "running" if is_running else "idle",
                     "timestamp": time.time(),
                     "client_id": self.client_id,
-                    "current_file": state.current_playing_file,
+                    "current_file": state.current_playing_file or '',
                     "speed": state.speed,
                     "position": {
                         "theta": state.current_theta,
@@ -273,22 +311,57 @@ class MQTTHandler(BaseMQTTHandler):
             self.status_thread = threading.Thread(target=self.publish_status, daemon=True)
             self.status_thread.start()
             
-            # Get initial states
+            # Get initial states from modules
+            is_running = bool(state.current_playing_file) and not state.stop_requested
+            serial_connected = is_connected()
+            serial_port = get_port() if serial_connected else None
             patterns = list_theta_rho_files()
             playlists = list_all_playlists()
-            serial_status = is_connected()
-            
-            logger.info(patterns, playlists, serial_status)
-            # Wait for connection
+
+            # Wait a bit for MQTT connection to establish
             time.sleep(1)
 
-            # Publish initial states
-            self.update_state(
-                is_running=not state.stop_requested,
-                current_file=state.current_playing_file,
-                patterns=patterns,
-                serial=serial_status.get('status', '')
-            )
+            # Publish initial state
+            status = {
+                "status": "running" if is_running else "idle",
+                "timestamp": time.time(),
+                "client_id": self.client_id,
+                "current_file": state.current_playing_file or ''
+            }
+            self.client.publish(self.status_topic, json.dumps(status), retain=True)
+            self.client.publish(self.running_state_topic, 
+                              "ON" if is_running else "OFF", 
+                              retain=True)
+            
+            # Format and publish serial state
+            serial_status = f"connected to {serial_port}" if serial_connected else "disconnected"
+            self.client.publish(self.serial_state_topic, serial_status, retain=True)
+            
+            # Update and publish pattern list
+            self.patterns = patterns
+            
+            # Update and publish playlist list
+            self.playlists = playlists
+            
+            # Get and publish playlist state
+            playlist_info = None
+            if state.current_playlist:
+                playlist_info = {
+                    'current_playlist': state.current_playlist
+                }
+            
+            self.client.publish(f"{self.device_id}/state/playlist", json.dumps({
+                "active": bool(playlist_info)
+            }), retain=True)
+
+            # Update playlist select state if a playlist is active
+            if playlist_info and playlist_info['current_playlist']:
+                current_playlist_name = playlist_info['current_playlist'][0]
+                self.client.publish(f"{self.playlist_select_topic}/state", current_playlist_name, retain=True)
+            else:
+                self.client.publish(f"{self.playlist_select_topic}/state", "", retain=True)
+
+            self.setup_ha_discovery()
             
             logger.info("MQTT Handler started successfully")
         except Exception as e:

+ 25 - 22
dune_weaver_flask/modules/mqtt/utils.py

@@ -4,27 +4,30 @@ from typing import Dict, Callable
 from dune_weaver_flask.modules.core.pattern_manager import (
     run_theta_rho_file, stop_actions, pause_execution,
     resume_execution, THETA_RHO_DIR,
-    run_theta_rho_files
+    run_theta_rho_files, list_theta_rho_files
+)
+from dune_weaver_flask.modules.core.playlist_manager import get_playlist, run_playlist
+from dune_weaver_flask.modules.serial.serial_manager import (
+    is_connected, get_port, home
 )
-from dune_weaver_flask.modules.core.playlist_manager import get_playlist
-from dune_weaver_flask.modules.serial.serial_manager import is_connected, home
 from dune_weaver_flask.modules.core.state import state
 
 def create_mqtt_callbacks() -> Dict[str, Callable]:
     """Create and return the MQTT callback registry."""
     def set_speed(speed):
         state.speed = speed
+
     return {
-        'run_pattern': run_theta_rho_file,  # Already handles file path
-        'run_playlist': lambda name: run_theta_rho_files(
-            [os.path.join(THETA_RHO_DIR, file) for file in get_playlist(name)['files']],
-            run_mode='loop',
-            pause_time=0,
-            clear_pattern=None
+        'run_pattern': lambda file_path: run_theta_rho_file(file_path),
+        'run_playlist': lambda playlist_name: run_playlist(
+            playlist_name,
+            run_mode="loop",  # Default to loop mode
+            pause_time=0,  # No pause between patterns
+            clear_pattern=None  # No clearing between patterns
         ),
-        'stop': stop_actions,  # Already handles state
-        'pause': pause_execution,  # Already handles state
-        'resume': resume_execution,  # Already handles state
+        'stop': stop_actions,
+        'pause': pause_execution,
+        'resume': resume_execution,
         'home': home,
         'set_speed': set_speed
     }
@@ -32,22 +35,22 @@ def create_mqtt_callbacks() -> Dict[str, Callable]:
 def get_mqtt_state():
     """Get the current state for MQTT updates."""
     # Get list of pattern files
-    patterns = []
-    for root, _, filenames in os.walk(THETA_RHO_DIR):
-        for file in filenames:
-            if file.endswith('.thr'):
-                patterns.append(file)
+    patterns = list_theta_rho_files()
     
     # Get current execution status
-    is_running = not state.stop_requested and not state.pause_requested
-    current_file = state.current_playing_file or ''
+    is_running = bool(state.current_playing_file) and not state.stop_requested
     
     # Get serial status
-    serial_status = is_connected()
+    serial_connected = is_connected()
+    serial_port = get_port() if serial_connected else None
+    serial_status = f"connected to {serial_port}" if serial_connected else "disconnected"
     
     return {
         'is_running': is_running,
-        'current_file': current_file,
+        'current_file': state.current_playing_file or '',
         'patterns': sorted(patterns),
-        'serial': serial_status.get('status', ''),
+        'serial': serial_status,
+        'current_playlist': state.current_playlist,
+        'current_playlist_index': state.current_playlist_index,
+        'playlist_mode': state.playlist_mode
     }