Parcourir la source

Add Still sands option to finish current pattern

tuanchris il y a 2 mois
Parent
commit
7f2871e65c
5 fichiers modifiés avec 77 ajouts et 5 suppressions
  1. 5 1
      main.py
  2. 30 1
      modules/core/pattern_manager.py
  3. 3 0
      modules/core/state.py
  4. 16 0
      static/js/settings.js
  5. 23 3
      templates/settings.html

+ 5 - 1
main.py

@@ -268,6 +268,7 @@ class TimeSlot(BaseModel):
 class ScheduledPauseRequest(BaseModel):
 class ScheduledPauseRequest(BaseModel):
     enabled: bool
     enabled: bool
     control_wled: Optional[bool] = False
     control_wled: Optional[bool] = False
+    finish_pattern: Optional[bool] = False  # Finish current pattern before pausing
     time_slots: List[TimeSlot] = []
     time_slots: List[TimeSlot] = []
 
 
 class CoordinateRequest(BaseModel):
 class CoordinateRequest(BaseModel):
@@ -440,6 +441,7 @@ async def get_scheduled_pause():
     return {
     return {
         "enabled": state.scheduled_pause_enabled,
         "enabled": state.scheduled_pause_enabled,
         "control_wled": state.scheduled_pause_control_wled,
         "control_wled": state.scheduled_pause_control_wled,
+        "finish_pattern": state.scheduled_pause_finish_pattern,
         "time_slots": state.scheduled_pause_time_slots
         "time_slots": state.scheduled_pause_time_slots
     }
     }
 
 
@@ -485,11 +487,13 @@ async def set_scheduled_pause(request: ScheduledPauseRequest):
         # Update state
         # Update state
         state.scheduled_pause_enabled = request.enabled
         state.scheduled_pause_enabled = request.enabled
         state.scheduled_pause_control_wled = request.control_wled
         state.scheduled_pause_control_wled = request.control_wled
+        state.scheduled_pause_finish_pattern = request.finish_pattern
         state.scheduled_pause_time_slots = [slot.model_dump() for slot in request.time_slots]
         state.scheduled_pause_time_slots = [slot.model_dump() for slot in request.time_slots]
         state.save()
         state.save()
 
 
         wled_msg = " (with WLED control)" if request.control_wled else ""
         wled_msg = " (with WLED control)" if request.control_wled else ""
-        logger.info(f"Still Sands {'enabled' if request.enabled else 'disabled'} with {len(request.time_slots)} time slots{wled_msg}")
+        finish_msg = " (finish pattern first)" if request.finish_pattern else ""
+        logger.info(f"Still Sands {'enabled' if request.enabled else 'disabled'} with {len(request.time_slots)} time slots{wled_msg}{finish_msg}")
         return {"success": True, "message": "Still Sands settings updated"}
         return {"success": True, "message": "Still Sands settings updated"}
 
 
     except HTTPException:
     except HTTPException:

+ 30 - 1
modules/core/pattern_manager.py

@@ -714,7 +714,8 @@ async def run_theta_rho_file(file_path, is_playlist=False):
 
 
                 # Wait for resume if paused (manual or scheduled)
                 # Wait for resume if paused (manual or scheduled)
                 manual_pause = state.pause_requested
                 manual_pause = state.pause_requested
-                scheduled_pause = is_in_scheduled_pause_period()
+                # Only check scheduled pause during pattern if "finish pattern first" is NOT enabled
+                scheduled_pause = is_in_scheduled_pause_period() if not state.scheduled_pause_finish_pattern else False
 
 
                 if manual_pause or scheduled_pause:
                 if manual_pause or scheduled_pause:
                     if manual_pause and scheduled_pause:
                     if manual_pause and scheduled_pause:
@@ -879,6 +880,34 @@ async def run_theta_rho_files(file_paths, pause_time=0, clear_pattern=None, run_
                 # Execute the pattern
                 # Execute the pattern
                 await run_theta_rho_file(file_path, is_playlist=True)
                 await run_theta_rho_file(file_path, is_playlist=True)
 
 
+                # Check for scheduled pause after pattern completes (when "finish pattern first" is enabled)
+                if state.scheduled_pause_finish_pattern and is_in_scheduled_pause_period() and not state.stop_requested:
+                    logger.info("Pattern completed. Entering Still Sands period (finish pattern first mode)...")
+
+                    # Turn off LED controller if control_wled is enabled
+                    wled_was_off_for_scheduled = False
+                    if state.scheduled_pause_control_wled and state.led_controller:
+                        logger.info("Turning off LED lights during Still Sands period")
+                        state.led_controller.set_power(0)
+                        wled_was_off_for_scheduled = True
+                    elif state.led_controller:
+                        state.led_controller.effect_idle(state.dw_led_idle_effect)
+                        start_idle_led_timeout()
+
+                    # Wait until we're outside the scheduled pause period
+                    while is_in_scheduled_pause_period() and not state.stop_requested:
+                        await asyncio.sleep(1)
+
+                    if not state.stop_requested:
+                        logger.info("Still Sands period ended. Resuming playlist...")
+                        if state.led_controller:
+                            if wled_was_off_for_scheduled:
+                                logger.info("Turning LED lights back on as Still Sands period ended")
+                                state.led_controller.set_power(1)
+                                await asyncio.sleep(0.5)  # Critical delay for LED controller
+                            state.led_controller.effect_playing(state.dw_led_playing_effect)
+                            idle_timeout_manager.cancel_timeout()
+
                 # Handle pause between patterns
                 # Handle pause between patterns
                 if idx < len(pattern_sequence) - 1 and not state.stop_requested and pause_time > 0 and not state.skip_requested:
                 if idx < len(pattern_sequence) - 1 and not state.stop_requested and pause_time > 0 and not state.skip_requested:
                     # Check if current pattern is a clear pattern
                     # Check if current pattern is a clear pattern

+ 3 - 0
modules/core/state.py

@@ -96,6 +96,7 @@ class AppState:
         self.scheduled_pause_enabled = False
         self.scheduled_pause_enabled = False
         self.scheduled_pause_time_slots = []  # List of time slot dictionaries
         self.scheduled_pause_time_slots = []  # List of time slot dictionaries
         self.scheduled_pause_control_wled = False  # Turn off WLED during pause periods
         self.scheduled_pause_control_wled = False  # Turn off WLED during pause periods
+        self.scheduled_pause_finish_pattern = False  # Finish current pattern before pausing
 
 
         # MQTT settings (UI-configurable, overrides .env if set)
         # MQTT settings (UI-configurable, overrides .env if set)
         self.mqtt_enabled = False  # Master enable/disable for MQTT
         self.mqtt_enabled = False  # Master enable/disable for MQTT
@@ -255,6 +256,7 @@ class AppState:
             "scheduled_pause_enabled": self.scheduled_pause_enabled,
             "scheduled_pause_enabled": self.scheduled_pause_enabled,
             "scheduled_pause_time_slots": self.scheduled_pause_time_slots,
             "scheduled_pause_time_slots": self.scheduled_pause_time_slots,
             "scheduled_pause_control_wled": self.scheduled_pause_control_wled,
             "scheduled_pause_control_wled": self.scheduled_pause_control_wled,
+            "scheduled_pause_finish_pattern": self.scheduled_pause_finish_pattern,
             "mqtt_enabled": self.mqtt_enabled,
             "mqtt_enabled": self.mqtt_enabled,
             "mqtt_broker": self.mqtt_broker,
             "mqtt_broker": self.mqtt_broker,
             "mqtt_port": self.mqtt_port,
             "mqtt_port": self.mqtt_port,
@@ -334,6 +336,7 @@ class AppState:
         self.scheduled_pause_enabled = data.get("scheduled_pause_enabled", False)
         self.scheduled_pause_enabled = data.get("scheduled_pause_enabled", False)
         self.scheduled_pause_time_slots = data.get("scheduled_pause_time_slots", [])
         self.scheduled_pause_time_slots = data.get("scheduled_pause_time_slots", [])
         self.scheduled_pause_control_wled = data.get("scheduled_pause_control_wled", False)
         self.scheduled_pause_control_wled = data.get("scheduled_pause_control_wled", False)
+        self.scheduled_pause_finish_pattern = data.get("scheduled_pause_finish_pattern", False)
         self.mqtt_enabled = data.get("mqtt_enabled", False)
         self.mqtt_enabled = data.get("mqtt_enabled", False)
         self.mqtt_broker = data.get("mqtt_broker", "")
         self.mqtt_broker = data.get("mqtt_broker", "")
         self.mqtt_port = data.get("mqtt_port", 1883)
         self.mqtt_port = data.get("mqtt_port", 1883)

+ 16 - 0
static/js/settings.js

@@ -1339,6 +1339,7 @@ async function initializeStillSandsMode() {
     const saveStillSandsButton = document.getElementById('savePauseSettings');
     const saveStillSandsButton = document.getElementById('savePauseSettings');
     const timeSlotsContainer = document.getElementById('timeSlotsContainer');
     const timeSlotsContainer = document.getElementById('timeSlotsContainer');
     const wledControlToggle = document.getElementById('stillSandsWledControl');
     const wledControlToggle = document.getElementById('stillSandsWledControl');
+    const finishPatternToggle = document.getElementById('stillSandsFinishPattern');
 
 
     // Check if elements exist
     // Check if elements exist
     if (!stillSandsToggle || !stillSandsSettings || !addTimeSlotButton || !saveStillSandsButton || !timeSlotsContainer) {
     if (!stillSandsToggle || !stillSandsSettings || !addTimeSlotButton || !saveStillSandsButton || !timeSlotsContainer) {
@@ -1377,6 +1378,11 @@ async function initializeStillSandsMode() {
             wledControlToggle.checked = data.control_wled || false;
             wledControlToggle.checked = data.control_wled || false;
         }
         }
 
 
+        // Load finish pattern setting
+        if (finishPatternToggle) {
+            finishPatternToggle.checked = data.finish_pattern || false;
+        }
+
         // Load existing time slots
         // Load existing time slots
         timeSlots = data.time_slots || [];
         timeSlots = data.time_slots || [];
 
 
@@ -1601,6 +1607,7 @@ async function initializeStillSandsMode() {
                 body: JSON.stringify({
                 body: JSON.stringify({
                     enabled: stillSandsToggle.checked,
                     enabled: stillSandsToggle.checked,
                     control_wled: wledControlToggle ? wledControlToggle.checked : false,
                     control_wled: wledControlToggle ? wledControlToggle.checked : false,
+                    finish_pattern: finishPatternToggle ? finishPatternToggle.checked : false,
                     time_slots: timeSlots.map(slot => ({
                     time_slots: timeSlots.map(slot => ({
                         start_time: slot.start_time,
                         start_time: slot.start_time,
                         end_time: slot.end_time,
                         end_time: slot.end_time,
@@ -1664,6 +1671,15 @@ async function initializeStillSandsMode() {
             await saveStillSandsSettings();
             await saveStillSandsSettings();
         });
         });
     }
     }
+
+    // Add listener for finish pattern toggle
+    if (finishPatternToggle) {
+        finishPatternToggle.addEventListener('change', async () => {
+            logMessage(`Finish pattern toggle changed: ${finishPatternToggle.checked}`, LOG_TYPE.INFO);
+            // Auto-save when finish pattern setting changes
+            await saveStillSandsSettings();
+        });
+    }
 }
 }
 
 
 // Homing Configuration
 // Homing Configuration

+ 23 - 3
templates/settings.html

@@ -1076,12 +1076,31 @@ input:checked + .slider:before {
       </div>
       </div>
 
 
       <div id="scheduledPauseSettings" class="space-y-4" style="display: none;">
       <div id="scheduledPauseSettings" class="space-y-4" style="display: none;">
+        <!-- Finish Current Pattern Option -->
+        <div class="bg-sky-50 rounded-lg p-4 border border-sky-200">
+          <div class="flex items-center justify-between">
+            <div class="flex-1">
+              <h4 class="text-slate-800 text-sm font-medium flex items-center gap-2">
+                <span class="material-icons text-slate-800 dark:text-slate-200 text-base">hourglass_bottom</span>
+                Finish Current Pattern
+              </h4>
+              <p class="text-xs text-slate-600 mt-1">
+                Let the current pattern complete before entering still mode
+              </p>
+            </div>
+            <label class="switch">
+              <input type="checkbox" id="stillSandsFinishPattern">
+              <span class="slider round"></span>
+            </label>
+          </div>
+        </div>
+
         <!-- WLED Control Option -->
         <!-- WLED Control Option -->
-        <div class="bg-amber-50 rounded-lg p-4 border border-amber-200">
+        <div class="bg-sky-50 rounded-lg p-4 border border-sky-200">
           <div class="flex items-center justify-between">
           <div class="flex items-center justify-between">
             <div class="flex-1">
             <div class="flex-1">
               <h4 class="text-slate-800 text-sm font-medium flex items-center gap-2">
               <h4 class="text-slate-800 text-sm font-medium flex items-center gap-2">
-                <span class="material-icons text-amber-600 text-base">lightbulb</span>
+                <span class="material-icons text-slate-800 dark:text-slate-200 text-base">lightbulb</span>
                 Control WLED Lights
                 Control WLED Lights
               </h4>
               </h4>
               <p class="text-xs text-slate-600 mt-1">
               <p class="text-xs text-slate-600 mt-1">
@@ -1121,7 +1140,8 @@ input:checked + .slider:before {
                 <p class="font-medium text-blue-800">Important Notes:</p>
                 <p class="font-medium text-blue-800">Important Notes:</p>
                 <ul class="mt-1 space-y-1 text-blue-700">
                 <ul class="mt-1 space-y-1 text-blue-700">
                   <li>• Times are based on your system's local time zone</li>
                   <li>• Times are based on your system's local time zone</li>
-                  <li>• Currently running patterns will pause immediately when entering a still period</li>
+                  <li>• By default, patterns pause immediately when entering a still period</li>
+                  <li>• Enable "Finish Current Pattern" to let patterns complete first</li>
                   <li>• Patterns will resume automatically when exiting a still period</li>
                   <li>• Patterns will resume automatically when exiting a still period</li>
                   <li>• Still periods that span midnight (e.g., 22:00 to 06:00) are supported</li>
                   <li>• Still periods that span midnight (e.g., 22:00 to 06:00) are supported</li>
                 </ul>
                 </ul>