import { useState, useEffect, useCallback } from 'react' import { Link } from 'react-router-dom' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Label } from '@/components/ui/label' import { Separator } from '@/components/ui/separator' import { Switch } from '@/components/ui/switch' import { Slider } from '@/components/ui/slider' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Input } from '@/components/ui/input' import { ColorPicker } from '@/components/ui/color-picker' // Types interface LedConfig { provider: 'none' | 'wled' | 'dw_leds' wled_ip?: string num_leds?: number gpio_pin?: number } interface DWLedsStatus { connected: boolean power_on: boolean brightness: number speed: number intensity: number current_effect: number current_palette: number num_leds: number gpio_pin: number colors: string[] error?: string } interface EffectSettings { effect_id: number palette_id: number speed: number intensity: number color1: string color2: string color3: string } export function LEDPage() { const [ledConfig, setLedConfig] = useState(null) const [isLoading, setIsLoading] = useState(true) // DW LEDs state const [dwStatus, setDwStatus] = useState(null) const [effects, setEffects] = useState<[number, string][]>([]) const [palettes, setPalettes] = useState<[number, string][]>([]) const [brightness, setBrightness] = useState(35) const [speed, setSpeed] = useState(128) const [intensity, setIntensity] = useState(128) const [selectedEffect, setSelectedEffect] = useState('') const [selectedPalette, setSelectedPalette] = useState('') const [color1, setColor1] = useState('#ff0000') const [color2, setColor2] = useState('#000000') const [color3, setColor3] = useState('#0000ff') // Effect automation state const [idleEffect, setIdleEffect] = useState(null) const [playingEffect, setPlayingEffect] = useState(null) const [idleTimeoutEnabled, setIdleTimeoutEnabled] = useState(false) const [idleTimeoutMinutes, setIdleTimeoutMinutes] = useState(30) // Fetch LED configuration useEffect(() => { const fetchConfig = async () => { try { const response = await fetch('/get_led_config') const data = await response.json() // Map backend response fields to our interface setLedConfig({ provider: data.provider || 'none', wled_ip: data.wled_ip, num_leds: data.dw_led_num_leds, gpio_pin: data.dw_led_gpio_pin, }) } catch (error) { console.error('Error fetching LED config:', error) } finally { setIsLoading(false) } } fetchConfig() }, []) // Initialize DW LEDs when provider is dw_leds useEffect(() => { if (ledConfig?.provider === 'dw_leds') { fetchDWLedsStatus() fetchEffectsAndPalettes() fetchEffectSettings() fetchIdleTimeout() } }, [ledConfig]) const fetchDWLedsStatus = async () => { try { const response = await fetch('/api/dw_leds/status') const data = await response.json() setDwStatus(data) if (data.connected) { setBrightness(data.brightness || 35) setSpeed(data.speed || 128) setIntensity(data.intensity || 128) setSelectedEffect(String(data.current_effect || 0)) setSelectedPalette(String(data.current_palette || 0)) if (data.colors) { setColor1(data.colors[0] || '#ff0000') setColor2(data.colors[1] || '#000000') setColor3(data.colors[2] || '#0000ff') } } } catch (error) { console.error('Error fetching DW LEDs status:', error) } } const fetchEffectsAndPalettes = async () => { try { const [effectsRes, palettesRes] = await Promise.all([ fetch('/api/dw_leds/effects'), fetch('/api/dw_leds/palettes'), ]) const effectsData = await effectsRes.json() const palettesData = await palettesRes.json() if (effectsData.effects) { const sorted = [...effectsData.effects].sort((a, b) => a[1].localeCompare(b[1])) setEffects(sorted) } if (palettesData.palettes) { const sorted = [...palettesData.palettes].sort((a, b) => a[1].localeCompare(b[1])) setPalettes(sorted) } } catch (error) { console.error('Error fetching effects/palettes:', error) } } const fetchEffectSettings = async () => { try { const response = await fetch('/api/dw_leds/get_effect_settings') const data = await response.json() setIdleEffect(data.idle_effect || null) setPlayingEffect(data.playing_effect || null) } catch (error) { console.error('Error fetching effect settings:', error) } } const fetchIdleTimeout = async () => { try { const response = await fetch('/api/dw_leds/idle_timeout') const data = await response.json() setIdleTimeoutEnabled(data.enabled || false) setIdleTimeoutMinutes(data.minutes || 30) } catch (error) { console.error('Error fetching idle timeout:', error) } } const handlePowerToggle = async () => { try { const response = await fetch('/api/dw_leds/power', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ state: 2 }), // Toggle }) const data = await response.json() if (data.connected) { toast.success(`Power ${data.power_on ? 'ON' : 'OFF'}`) await fetchDWLedsStatus() } else { toast.error(data.error || 'Failed to toggle power') } } catch (error) { toast.error('Failed to toggle power') } } const handleBrightnessChange = useCallback(async (value: number[]) => { setBrightness(value[0]) }, []) const handleBrightnessCommit = async (value: number[]) => { try { const response = await fetch('/api/dw_leds/brightness', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: value[0] }), }) const data = await response.json() if (data.connected) { toast.success(`Brightness: ${value[0]}%`) } } catch (error) { toast.error('Failed to set brightness') } } const handleSpeedChange = useCallback((value: number[]) => { setSpeed(value[0]) }, []) const handleSpeedCommit = async (value: number[]) => { try { await fetch('/api/dw_leds/speed', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ speed: value[0] }), }) toast.success(`Speed: ${value[0]}`) } catch (error) { toast.error('Failed to set speed') } } const handleIntensityChange = useCallback((value: number[]) => { setIntensity(value[0]) }, []) const handleIntensityCommit = async (value: number[]) => { try { await fetch('/api/dw_leds/intensity', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ intensity: value[0] }), }) toast.success(`Intensity: ${value[0]}`) } catch (error) { toast.error('Failed to set intensity') } } const handleEffectChange = async (value: string) => { setSelectedEffect(value) try { const response = await fetch('/api/dw_leds/effect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ effect_id: parseInt(value) }), }) const data = await response.json() if (data.connected) { toast.success('Effect changed') if (data.power_on !== undefined) { setDwStatus((prev) => prev ? { ...prev, power_on: data.power_on } : null) } } } catch (error) { toast.error('Failed to set effect') } } const handlePaletteChange = async (value: string) => { setSelectedPalette(value) try { const response = await fetch('/api/dw_leds/palette', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ palette_id: parseInt(value) }), }) const data = await response.json() if (data.connected) { toast.success('Palette changed') } } catch (error) { toast.error('Failed to set palette') } } const handleColorChange = async (slot: 1 | 2 | 3, value: string) => { if (slot === 1) setColor1(value) else if (slot === 2) setColor2(value) else setColor3(value) // Debounce color changes try { const hexToRgb = (hex: string) => { const r = parseInt(hex.slice(1, 3), 16) const g = parseInt(hex.slice(3, 5), 16) const b = parseInt(hex.slice(5, 7), 16) return [r, g, b] } const payload: Record = {} payload[`color${slot}`] = hexToRgb(value) await fetch('/api/dw_leds/colors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } catch (error) { console.error('Failed to set color:', error) } } const saveCurrentEffectSettings = async (type: 'idle' | 'playing') => { try { const settings = { type, effect_id: parseInt(selectedEffect) || 0, palette_id: parseInt(selectedPalette) || 0, speed, intensity, color1, color2, color3, } await fetch('/api/dw_leds/save_effect_settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings), }) toast.success(`${type.charAt(0).toUpperCase() + type.slice(1)} effect saved`) await fetchEffectSettings() } catch (error) { toast.error(`Failed to save ${type} effect`) } } const clearEffectSettings = async (type: 'idle' | 'playing') => { try { await fetch('/api/dw_leds/clear_effect_settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type }), }) toast.success(`${type.charAt(0).toUpperCase() + type.slice(1)} effect cleared`) await fetchEffectSettings() } catch (error) { toast.error(`Failed to clear ${type} effect`) } } const saveIdleTimeout = async (enabled?: boolean, minutes?: number) => { const finalEnabled = enabled !== undefined ? enabled : idleTimeoutEnabled const finalMinutes = minutes !== undefined ? minutes : idleTimeoutMinutes try { await fetch('/api/dw_leds/idle_timeout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: finalEnabled, minutes: finalMinutes }), }) toast.success(`Idle timeout ${finalEnabled ? 'enabled' : 'disabled'}`) } catch (error) { toast.error('Failed to save idle timeout') } } const handleIdleTimeoutToggle = async (checked: boolean) => { setIdleTimeoutEnabled(checked) await saveIdleTimeout(checked, idleTimeoutMinutes) } const formatEffectSettings = (settings: EffectSettings | null) => { if (!settings) return 'Not configured' const effectName = effects.find((e) => e[0] === settings.effect_id)?.[1] || settings.effect_id const paletteName = palettes.find((p) => p[0] === settings.palette_id)?.[1] || settings.palette_id return `${effectName} | ${paletteName} | Speed: ${settings.speed} | Intensity: ${settings.intensity}` } // Loading state if (isLoading) { return (
sync
) } // Not configured state if (!ledConfig || ledConfig.provider === 'none') { return (
lightbulb

LED Controller Not Configured

Configure your LED controller (WLED or DW LEDs) in the Settings page to control your lights.

) } // WLED iframe view if (ledConfig.provider === 'wled' && ledConfig.wled_ip) { return (