Procházet zdrojové kódy

add full customization for idle + playing effect

tuanchris před 3 měsíci
rodič
revize
e767aa8d2f
5 změnil soubory, kde provedl 351 přidání a 78 odebrání
  1. 44 10
      main.py
  2. 23 4
      modules/core/state.py
  3. 81 25
      modules/led/dw_led_controller.py
  4. 141 19
      static/js/led-control.js
  5. 62 20
      templates/wled.html

+ 44 - 10
main.py

@@ -1656,20 +1656,54 @@ async def dw_leds_intensity(request: dict):
         logger.error(f"Failed to set DW LED intensity: {str(e)}")
         raise HTTPException(status_code=500, detail=str(e))
 
-@app.post("/api/dw_leds/set_effects")
-async def dw_leds_set_effects(request: dict):
-    """Configure idle and playing effects"""
-    idle_effect = request.get("idle_effect")
-    playing_effect = request.get("playing_effect")
-
-    state.dw_led_idle_effect = idle_effect if idle_effect else "off"
-    state.dw_led_playing_effect = playing_effect if playing_effect else "off"
+@app.post("/api/dw_leds/save_effect_settings")
+async def dw_leds_save_effect_settings(request: dict):
+    """Save current LED settings as idle or playing effect"""
+    effect_type = request.get("type")  # 'idle' or 'playing'
+
+    settings = {
+        "effect_id": request.get("effect_id"),
+        "palette_id": request.get("palette_id"),
+        "speed": request.get("speed"),
+        "intensity": request.get("intensity"),
+        "color1": request.get("color1"),
+        "color2": request.get("color2"),
+        "color3": request.get("color3")
+    }
+
+    if effect_type == "idle":
+        state.dw_led_idle_effect = settings
+    elif effect_type == "playing":
+        state.dw_led_playing_effect = settings
+    else:
+        raise HTTPException(status_code=400, detail="Invalid effect type. Must be 'idle' or 'playing'")
+
     state.save()
+    logger.info(f"DW LED {effect_type} effect settings saved: {settings}")
+
+    return {"success": True, "type": effect_type, "settings": settings}
 
-    logger.info(f"DW LED effects configured - Idle: {state.dw_led_idle_effect}, Playing: {state.dw_led_playing_effect}")
+@app.post("/api/dw_leds/clear_effect_settings")
+async def dw_leds_clear_effect_settings(request: dict):
+    """Clear idle or playing effect settings"""
+    effect_type = request.get("type")  # 'idle' or 'playing'
 
+    if effect_type == "idle":
+        state.dw_led_idle_effect = None
+    elif effect_type == "playing":
+        state.dw_led_playing_effect = None
+    else:
+        raise HTTPException(status_code=400, detail="Invalid effect type. Must be 'idle' or 'playing'")
+
+    state.save()
+    logger.info(f"DW LED {effect_type} effect settings cleared")
+
+    return {"success": True, "type": effect_type}
+
+@app.get("/api/dw_leds/get_effect_settings")
+async def dw_leds_get_effect_settings():
+    """Get saved idle and playing effect settings"""
     return {
-        "success": True,
         "idle_effect": state.dw_led_idle_effect,
         "playing_effect": state.dw_led_playing_effect
     }

+ 23 - 4
modules/core/state.py

@@ -50,8 +50,12 @@ class AppState:
         self.dw_led_brightness = 35  # Brightness 0-100
         self.dw_led_speed = 128  # Effect speed 0-255
         self.dw_led_intensity = 128  # Effect intensity 0-255
-        self.dw_led_idle_effect = "off"  # Effect to show when idle
-        self.dw_led_playing_effect = "off"  # Effect to show when playing
+
+        # Idle effect settings (all parameters)
+        self.dw_led_idle_effect = None  # Full effect configuration dict or None
+
+        # Playing effect settings (all parameters)
+        self.dw_led_playing_effect = None  # Full effect configuration dict or None
         self.skip_requested = False
         self.table_type = None
         self._playlist_mode = "loop"
@@ -256,8 +260,23 @@ class AppState:
         self.dw_led_brightness = data.get('dw_led_brightness', 35)
         self.dw_led_speed = data.get('dw_led_speed', 128)
         self.dw_led_intensity = data.get('dw_led_intensity', 128)
-        self.dw_led_idle_effect = data.get('dw_led_idle_effect', "off")
-        self.dw_led_playing_effect = data.get('dw_led_playing_effect', "off")
+
+        # Load effect settings (handle both old string format and new dict format)
+        idle_effect_data = data.get('dw_led_idle_effect', None)
+        if isinstance(idle_effect_data, str):
+            # Old format: just effect name
+            self.dw_led_idle_effect = None if idle_effect_data == "off" else {"effect_id": 0}
+        else:
+            # New format: full dict or None
+            self.dw_led_idle_effect = idle_effect_data
+
+        playing_effect_data = data.get('dw_led_playing_effect', None)
+        if isinstance(playing_effect_data, str):
+            # Old format: just effect name
+            self.dw_led_playing_effect = None if playing_effect_data == "off" else {"effect_id": 0}
+        else:
+            # New format: full dict or None
+            self.dw_led_playing_effect = playing_effect_data
         self.app_name = data.get("app_name", "Dune Weaver")
         self.auto_play_enabled = data.get("auto_play_enabled", False)
         self.auto_play_playlist = data.get("auto_play_playlist", None)

+ 81 - 25
modules/led/dw_led_controller.py

@@ -506,19 +506,47 @@ def effect_loading(controller: DWLEDController) -> bool:
         return False
 
 
-def effect_idle(controller: DWLEDController, effect_name: Optional[str] = None) -> bool:
-    """Show idle effect"""
+def effect_idle(controller: DWLEDController, effect_settings: Optional[dict] = None) -> bool:
+    """Show idle effect with full settings"""
     try:
-        if effect_name and effect_name.lower() != "off":
-            # Try to find effect by name
-            effects = controller.get_effects()
-            for eid, name in effects:
-                if name.lower() == effect_name.lower():
-                    controller.set_power(1)
-                    controller.set_effect(eid)
-                    return True
-
-        # Default: turn off
+        if effect_settings and isinstance(effect_settings, dict):
+            # New format: full settings dict
+            controller.set_power(1)
+
+            # Set effect
+            effect_id = effect_settings.get("effect_id", 0)
+            palette_id = effect_settings.get("palette_id", 0)
+            speed = effect_settings.get("speed", 128)
+            intensity = effect_settings.get("intensity", 128)
+
+            controller.set_effect(effect_id, speed=speed, intensity=intensity)
+            controller.set_palette(palette_id)
+
+            # Set colors if provided
+            color1 = effect_settings.get("color1")
+            if color1:
+                # Convert hex to RGB
+                r1 = int(color1[1:3], 16)
+                g1 = int(color1[3:5], 16)
+                b1 = int(color1[5:7], 16)
+
+                color2 = effect_settings.get("color2", "#000000")
+                r2 = int(color2[1:3], 16)
+                g2 = int(color2[3:5], 16)
+                b2 = int(color2[5:7], 16)
+
+                color3 = effect_settings.get("color3", "#0000ff")
+                r3 = int(color3[1:3], 16)
+                g3 = int(color3[3:5], 16)
+                b3 = int(color3[5:7], 16)
+
+                controller.set_color_slot(0, r1, g1, b1)
+                controller.set_color_slot(1, r2, g2, b2)
+                controller.set_color_slot(2, r3, g3, b3)
+
+            return True
+
+        # Default or old format: turn off
         controller.set_power(0)
         return True
     except Exception as e:
@@ -539,20 +567,48 @@ def effect_connected(controller: DWLEDController) -> bool:
         return False
 
 
-def effect_playing(controller: DWLEDController, effect_name: Optional[str] = None) -> bool:
-    """Show playing effect"""
+def effect_playing(controller: DWLEDController, effect_settings: Optional[dict] = None) -> bool:
+    """Show playing effect with full settings"""
     try:
-        if effect_name and effect_name.lower() != "off":
-            # Try to find effect by name
-            effects = controller.get_effects()
-            for eid, name in effects:
-                if name.lower() == effect_name.lower():
-                    controller.set_power(1)
-                    controller.set_effect(eid)
-                    return True
-        else:
-            # Default: turn off
-            controller.set_power(0)
+        if effect_settings and isinstance(effect_settings, dict):
+            # New format: full settings dict
+            controller.set_power(1)
+
+            # Set effect
+            effect_id = effect_settings.get("effect_id", 0)
+            palette_id = effect_settings.get("palette_id", 0)
+            speed = effect_settings.get("speed", 128)
+            intensity = effect_settings.get("intensity", 128)
+
+            controller.set_effect(effect_id, speed=speed, intensity=intensity)
+            controller.set_palette(palette_id)
+
+            # Set colors if provided
+            color1 = effect_settings.get("color1")
+            if color1:
+                # Convert hex to RGB
+                r1 = int(color1[1:3], 16)
+                g1 = int(color1[3:5], 16)
+                b1 = int(color1[5:7], 16)
+
+                color2 = effect_settings.get("color2", "#000000")
+                r2 = int(color2[1:3], 16)
+                g2 = int(color2[3:5], 16)
+                b2 = int(color2[5:7], 16)
+
+                color3 = effect_settings.get("color3", "#0000ff")
+                r3 = int(color3[1:3], 16)
+                g3 = int(color3[3:5], 16)
+                b3 = int(color3[5:7], 16)
+
+                controller.set_color_slot(0, r1, g1, b1)
+                controller.set_color_slot(1, r2, g2, b2)
+                controller.set_color_slot(2, r3, g3, b3)
+
+            return True
+
+        # Default or old format: turn off
+        controller.set_power(0)
         return True
     except Exception as e:
         logger.error(f"Error setting playing effect: {e}")

+ 141 - 19
static/js/led-control.js

@@ -285,29 +285,151 @@ async function initializeDWLedsControls() {
         }
     });
 
-    // Save automation effects button
-    document.getElementById('dw-leds-save-effects')?.addEventListener('click', async () => {
-        try {
-            const idleEffect = document.getElementById('dw-leds-idle-effect')?.value || 'off';
-            const playingEffect = document.getElementById('dw-leds-playing-effect')?.value || 'off';
+    // Save Current Idle Effect
+    document.getElementById('dw-leds-save-current-idle')?.addEventListener('click', async () => {
+        await saveCurrentEffectSettings('idle');
+    });
 
-            const response = await fetch('/api/dw_leds/set_effects', {
-                method: 'POST',
-                headers: { 'Content-Type': 'application/json' },
-                body: JSON.stringify({
-                    idle_effect: idleEffect,
-                    playing_effect: playingEffect
-                })
-            });
+    // Clear Idle Effect
+    document.getElementById('dw-leds-clear-idle')?.addEventListener('click', async () => {
+        await clearEffectSettings('idle');
+    });
 
-            if (!response.ok) throw new Error(`HTTP ${response.status}`);
+    // Save Current Playing Effect
+    document.getElementById('dw-leds-save-current-playing')?.addEventListener('click', async () => {
+        await saveCurrentEffectSettings('playing');
+    });
 
-            await response.json();
-            showStatus('Effect settings saved successfully', 'success');
-        } catch (error) {
-            showStatus(`Failed to save effect settings: ${error.message}`, 'error');
-        }
+    // Clear Playing Effect
+    document.getElementById('dw-leds-clear-playing')?.addEventListener('click', async () => {
+        await clearEffectSettings('playing');
     });
+
+    // Load and display saved effect settings
+    await loadEffectSettings();
+}
+
+// Save current LED settings as idle or playing effect
+async function saveCurrentEffectSettings(type) {
+    try {
+        const effectId = parseInt(document.getElementById('dw-leds-effect-select')?.value) || 0;
+        const paletteId = parseInt(document.getElementById('dw-leds-palette-select')?.value) || 0;
+        const speed = parseInt(document.getElementById('dw-leds-speed')?.value) || 128;
+        const intensity = parseInt(document.getElementById('dw-leds-intensity')?.value) || 128;
+
+        // Get effect colors
+        const color1 = document.getElementById('dw-leds-color1')?.value || '#ff0000';
+        const color2 = document.getElementById('dw-leds-color2')?.value || '#000000';
+        const color3 = document.getElementById('dw-leds-color3')?.value || '#0000ff';
+
+        const settings = {
+            type: type,  // 'idle' or 'playing'
+            effect_id: effectId,
+            palette_id: paletteId,
+            speed: speed,
+            intensity: intensity,
+            color1: color1,
+            color2: color2,
+            color3: color3
+        };
+
+        const response = await fetch('/api/dw_leds/save_effect_settings', {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify(settings)
+        });
+
+        if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
+        const data = await response.json();
+        showStatus(`${type.charAt(0).toUpperCase() + type.slice(1)} effect settings saved successfully`, 'success');
+
+        // Refresh display
+        await loadEffectSettings();
+    } catch (error) {
+        showStatus(`Failed to save ${type} effect settings: ${error.message}`, 'error');
+    }
+}
+
+// Clear effect settings
+async function clearEffectSettings(type) {
+    try {
+        const response = await fetch('/api/dw_leds/clear_effect_settings', {
+            method: 'POST',
+            headers: { 'Content-Type': 'application/json' },
+            body: JSON.stringify({ type: type })
+        });
+
+        if (!response.ok) throw new Error(`HTTP ${response.status}`);
+
+        showStatus(`${type.charAt(0).toUpperCase() + type.slice(1)} effect cleared`, 'success');
+
+        // Refresh display
+        await loadEffectSettings();
+    } catch (error) {
+        showStatus(`Failed to clear ${type} effect: ${error.message}`, 'error');
+    }
+}
+
+// Load and display saved effect settings
+async function loadEffectSettings() {
+    try {
+        const response = await fetch('/api/dw_leds/get_effect_settings');
+        if (!response.ok) return;
+
+        const data = await response.json();
+
+        // Display idle settings
+        const idleDisplay = document.getElementById('idle-settings-display');
+        if (idleDisplay) {
+            idleDisplay.textContent = formatEffectSettings(data.idle_effect);
+        }
+
+        // Display playing settings
+        const playingDisplay = document.getElementById('playing-settings-display');
+        if (playingDisplay) {
+            playingDisplay.textContent = formatEffectSettings(data.playing_effect);
+        }
+    } catch (error) {
+        console.error('Failed to load effect settings:', error);
+    }
+}
+
+// Format effect settings for display
+function formatEffectSettings(settings) {
+    if (!settings) {
+        return 'Not configured (LEDs will turn off)';
+    }
+
+    const parts = [];
+
+    // Get effect name from select (if available)
+    const effectSelect = document.getElementById('dw-leds-effect-select');
+    if (effectSelect && settings.effect_id !== undefined) {
+        const effectOption = effectSelect.querySelector(`option[value="${settings.effect_id}"]`);
+        parts.push(`Effect: ${effectOption ? effectOption.textContent : settings.effect_id}`);
+    }
+
+    // Get palette name from select (if available)
+    const paletteSelect = document.getElementById('dw-leds-palette-select');
+    if (paletteSelect && settings.palette_id !== undefined) {
+        const paletteOption = paletteSelect.querySelector(`option[value="${settings.palette_id}"]`);
+        parts.push(`Palette: ${paletteOption ? paletteOption.textContent : settings.palette_id}`);
+    }
+
+    if (settings.speed !== undefined) {
+        parts.push(`Speed: ${settings.speed}`);
+    }
+
+    if (settings.intensity !== undefined) {
+        parts.push(`Intensity: ${settings.intensity}`);
+    }
+
+    if (settings.color1) {
+        parts.push(`Colors: ${settings.color1}, ${settings.color2 || '#000000'}, ${settings.color3 || '#0000ff'}`);
+    }
+
+    return parts.join(' | ');
 }
 
 // Helper function to apply color

+ 62 - 20
templates/wled.html

@@ -253,32 +253,74 @@
       </div>
 
       <!-- Effect Settings -->
-      <div class="flex flex-col gap-4 pt-4 border-t border-slate-200">
-        <h3 class="text-slate-800 text-base font-semibold">Automation Settings</h3>
-        <p class="text-xs text-slate-500">Configure which effects to show when idle or playing patterns</p>
+      <div class="flex flex-col gap-6 pt-4 border-t border-slate-200">
+        <div>
+          <h3 class="text-slate-800 text-base font-semibold">Automation Settings</h3>
+          <p class="text-xs text-slate-500 mt-1">Configure LED effects to automatically activate when idle or playing patterns</p>
+        </div>
 
-        <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
-          <!-- Idle Effect -->
-          <div class="flex flex-col gap-2">
-            <label class="text-sm font-medium text-slate-700">Idle Effect</label>
-            <select id="dw-leds-idle-effect" class="form-select w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 focus:outline-0 focus:ring-2 focus:ring-blue-500">
-              <option value="off">Off</option>
-            </select>
+        <!-- Idle Effect Configuration -->
+        <div class="bg-slate-50 rounded-lg p-4 border border-slate-200">
+          <div class="flex items-center justify-between mb-3">
+            <h4 class="text-slate-700 text-sm font-semibold flex items-center gap-2">
+              <span class="material-icons text-blue-600 text-lg">bedtime</span>
+              Idle Effect
+            </h4>
+            <div class="flex gap-2">
+              <button id="dw-leds-save-current-idle" class="flex items-center gap-1.5 rounded-lg bg-green-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-green-700 transition-colors focus:outline-none focus:ring-2 focus:ring-green-400">
+                <span class="material-icons text-sm">save</span>
+                Save Current
+              </button>
+              <button id="dw-leds-clear-idle" class="flex items-center gap-1.5 rounded-lg bg-gray-500 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-gray-600 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400">
+                <span class="material-icons text-sm">clear</span>
+                Clear
+              </button>
+            </div>
           </div>
 
-          <!-- Playing Effect -->
-          <div class="flex flex-col gap-2">
-            <label class="text-sm font-medium text-slate-700">Playing Effect</label>
-            <select id="dw-leds-playing-effect" class="form-select w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 focus:outline-0 focus:ring-2 focus:ring-blue-500">
-              <option value="off">Off</option>
-            </select>
+          <div id="idle-current-settings" class="text-xs text-slate-600 mb-3 p-2 bg-white rounded border border-slate-200">
+            <span class="font-medium">Current:</span> <span id="idle-settings-display">Not configured</span>
+          </div>
+        </div>
+
+        <!-- Playing Effect Configuration -->
+        <div class="bg-slate-50 rounded-lg p-4 border border-slate-200">
+          <div class="flex items-center justify-between mb-3">
+            <h4 class="text-slate-700 text-sm font-semibold flex items-center gap-2">
+              <span class="material-icons text-blue-600 text-lg">play_circle</span>
+              Playing Effect
+            </h4>
+            <div class="flex gap-2">
+              <button id="dw-leds-save-current-playing" class="flex items-center gap-1.5 rounded-lg bg-green-600 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-green-700 transition-colors focus:outline-none focus:ring-2 focus:ring-green-400">
+                <span class="material-icons text-sm">save</span>
+                Save Current
+              </button>
+              <button id="dw-leds-clear-playing" class="flex items-center gap-1.5 rounded-lg bg-gray-500 px-3 py-1.5 text-xs font-semibold text-white shadow-sm hover:bg-gray-600 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400">
+                <span class="material-icons text-sm">clear</span>
+                Clear
+              </button>
+            </div>
+          </div>
+
+          <div id="playing-current-settings" class="text-xs text-slate-600 mb-3 p-2 bg-white rounded border border-slate-200">
+            <span class="font-medium">Current:</span> <span id="playing-settings-display">Not configured</span>
           </div>
         </div>
 
-        <button id="dw-leds-save-effects" class="flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-md hover:bg-blue-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2">
-          <span class="material-icons text-base">save</span>
-          <span>Save Effect Settings</span>
-        </button>
+        <div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
+          <div class="flex items-start gap-2">
+            <span class="material-icons text-blue-600 text-base">info</span>
+            <div class="text-xs text-blue-700">
+              <p class="font-medium text-blue-800">How to use:</p>
+              <ul class="mt-1 space-y-1 list-disc list-inside">
+                <li>Adjust LED settings above (effect, palette, speed, intensity, colors)</li>
+                <li>Click "Save Current" to capture current settings for automation</li>
+                <li>Click "Clear" to turn off automation for that state</li>
+                <li>Settings are applied automatically when table changes state</li>
+              </ul>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
   </section>