// LED Control Page - Unified interface for WLED and Hyperion
let ledConfig = null;
let hyperionController = null;
// Utility function to show status messages
function showStatus(message, type = 'info') {
const statusDiv = document.getElementById('hyperion-status');
if (!statusDiv) return;
const iconMap = {
'success': 'check_circle',
'error': 'error',
'warning': 'warning',
'info': 'info'
};
const colorMap = {
'success': 'text-green-700 bg-green-50 border-green-200',
'error': 'text-red-700 bg-red-50 border-red-200',
'warning': 'text-amber-700 bg-amber-50 border-amber-200',
'info': 'text-gray-700 bg-gray-100 border-slate-200'
};
const icon = iconMap[type] || 'info';
const colorClass = colorMap[type] || colorMap.info;
statusDiv.className = `p-4 rounded-lg border ${colorClass}`;
statusDiv.innerHTML = `
${icon}
${message}
`;
}
// Initialize the page based on LED configuration
async function initializeLedPage() {
try {
const response = await fetch('/get_led_config');
if (!response.ok) throw new Error('Failed to fetch LED config');
ledConfig = await response.json();
const notConfigured = document.getElementById('led-not-configured');
const wledContainer = document.getElementById('wled-container');
const hyperionContainer = document.getElementById('hyperion-container');
// Hide all containers first
notConfigured.classList.add('hidden');
wledContainer.classList.add('hidden');
hyperionContainer.classList.add('hidden');
if (ledConfig.provider === 'wled' && ledConfig.wled_ip) {
// Show WLED iframe
wledContainer.classList.remove('hidden');
const wledFrame = document.getElementById('wled-frame');
if (wledFrame) {
wledFrame.src = `http://${ledConfig.wled_ip}`;
}
} else if (ledConfig.provider === 'hyperion' && ledConfig.hyperion_ip) {
// Show Hyperion controls
hyperionContainer.classList.remove('hidden');
await initializeHyperionControls();
} else {
// Show not configured message
notConfigured.classList.remove('hidden');
}
} catch (error) {
console.error('Error initializing LED page:', error);
document.getElementById('led-not-configured').classList.remove('hidden');
}
}
// Initialize Hyperion controls
async function initializeHyperionControls() {
// Create API helper
hyperionController = {
async sendCommand(endpoint, data) {
try {
const response = await fetch(`/api/hyperion/${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
throw error;
}
}
};
// Check connection status and load effects
await checkHyperionStatus();
await loadEffectsList();
// Power toggle button
document.getElementById('hyperion-power-toggle')?.addEventListener('click', async () => {
try {
// Toggle using state 2
await hyperionController.sendCommand('power', { state: 2 });
showStatus('Power toggled', 'success');
await checkHyperionStatus();
} catch (error) {
showStatus(`Failed to toggle power: ${error.message}`, 'error');
}
});
// Brightness slider
const brightnessSlider = document.getElementById('hyperion-brightness');
const brightnessValue = document.getElementById('brightness-value');
brightnessSlider?.addEventListener('input', (e) => {
brightnessValue.textContent = `${e.target.value}%`;
});
brightnessSlider?.addEventListener('change', async (e) => {
try {
await hyperionController.sendCommand('brightness', { value: parseInt(e.target.value) });
showStatus(`Brightness set to ${e.target.value}%`, 'success');
} catch (error) {
showStatus(`Failed to set brightness: ${error.message}`, 'error');
}
});
// Color picker - update display when color changes
const colorPicker = document.getElementById('hyperion-color');
const colorHexDisplay = document.getElementById('color-hex-display');
colorPicker?.addEventListener('input', (e) => {
if (colorHexDisplay) {
colorHexDisplay.textContent = e.target.value.toUpperCase();
}
});
// Color picker - apply button
document.getElementById('hyperion-set-color')?.addEventListener('click', async () => {
const hexColor = colorPicker.value;
try {
await hyperionController.sendCommand('color', { hex: hexColor });
showStatus(`Color set to ${hexColor.toUpperCase()}`, 'success');
} catch (error) {
showStatus(`Failed to set color: ${error.message}`, 'error');
}
});
// Quick color buttons
document.querySelectorAll('.quick-color').forEach(button => {
button.addEventListener('click', async () => {
const hexColor = button.getAttribute('data-color');
try {
await hyperionController.sendCommand('color', { hex: hexColor });
showStatus(`Color set to ${hexColor.toUpperCase()}`, 'success');
// Update color picker and hex display to match
const colorPicker = document.getElementById('hyperion-color');
const colorHexDisplay = document.getElementById('color-hex-display');
if (colorPicker) colorPicker.value = hexColor;
if (colorHexDisplay) colorHexDisplay.textContent = hexColor.toUpperCase();
} catch (error) {
showStatus(`Failed to set color: ${error.message}`, 'error');
}
});
});
// Effects selection
document.getElementById('hyperion-set-effect')?.addEventListener('click', async () => {
const effectSelect = document.getElementById('hyperion-effect-select');
const effectName = effectSelect.value;
if (!effectName) {
showStatus('Please select an effect', 'warning');
return;
}
try {
await hyperionController.sendCommand('effect', { effect_name: effectName });
showStatus(`Effect '${effectName}' activated`, 'success');
} catch (error) {
showStatus(`Failed to set effect: ${error.message}`, 'error');
}
});
// Save effect settings button
document.getElementById('save-hyperion-effects')?.addEventListener('click', async () => {
try {
const idleEffect = document.getElementById('hyperion-idle-effect')?.value || '';
const playingEffect = document.getElementById('hyperion-playing-effect')?.value || '';
const response = await fetch('/api/hyperion/set_effects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
idle_effect: idleEffect,
playing_effect: playingEffect
})
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
await response.json();
showStatus('Effect settings saved successfully', 'success');
} catch (error) {
showStatus(`Failed to save effect settings: ${error.message}`, 'error');
}
});
}
// Load available Hyperion effects
async function loadEffectsList() {
try {
const response = await fetch('/api/hyperion/effects');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
const effectSelect = document.getElementById('hyperion-effect-select');
const idleEffectSelect = document.getElementById('hyperion-idle-effect');
const playingEffectSelect = document.getElementById('hyperion-playing-effect');
if (!effectSelect) return;
// Clear loading option
effectSelect.innerHTML = '';
// Add "Default (Off)" option to idle and playing effect selectors
if (idleEffectSelect) {
idleEffectSelect.innerHTML = '';
}
if (playingEffectSelect) {
playingEffectSelect.innerHTML = '';
}
// Add effects to all dropdowns
if (data.effects && data.effects.length > 0) {
data.effects.forEach(effect => {
// Main effect selector
const option = document.createElement('option');
option.value = effect.name;
option.textContent = effect.name;
effectSelect.appendChild(option);
// Idle effect selector
if (idleEffectSelect) {
const idleOption = document.createElement('option');
idleOption.value = effect.name;
idleOption.textContent = effect.name;
idleEffectSelect.appendChild(idleOption);
}
// Playing effect selector
if (playingEffectSelect) {
const playingOption = document.createElement('option');
playingOption.value = effect.name;
playingOption.textContent = effect.name;
playingEffectSelect.appendChild(playingOption);
}
});
// Load saved settings from config
const configResponse = await fetch('/get_led_config');
if (configResponse.ok) {
const config = await configResponse.json();
if (idleEffectSelect && config.hyperion_idle_effect) {
idleEffectSelect.value = config.hyperion_idle_effect;
}
if (playingEffectSelect && config.hyperion_playing_effect) {
playingEffectSelect.value = config.hyperion_playing_effect;
}
}
} else {
effectSelect.innerHTML = '';
}
} catch (error) {
console.error('Failed to load effects:', error);
const effectSelect = document.getElementById('hyperion-effect-select');
if (effectSelect) {
effectSelect.innerHTML = '';
}
}
}
// Check Hyperion connection status
async function checkHyperionStatus() {
try {
const response = await fetch('/api/hyperion/status');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data.connected) {
const version = data.version || 'unknown';
const hostname = data.hostname || 'unknown';
const isOn = data.is_on;
const state = isOn ? 'ON' : 'OFF';
// Update power button appearance - shows current state with appropriate action
const powerButton = document.getElementById('hyperion-power-toggle');
const powerButtonText = document.getElementById('power-button-text');
if (powerButton && powerButtonText) {
if (isOn) {
powerButton.className = 'flex items-center justify-center gap-2 rounded-lg bg-red-600 px-4 py-3 text-sm font-semibold text-white shadow-md hover:bg-red-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-offset-2';
powerButtonText.textContent = 'Turn OFF';
} else {
powerButton.className = 'flex items-center justify-center gap-2 rounded-lg bg-green-600 px-4 py-3 text-sm font-semibold text-white shadow-md hover:bg-green-700 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-offset-2';
powerButtonText.textContent = 'Turn ON';
}
}
showStatus(`Connected to ${hostname} (${version}) - Power: ${state}`, 'success');
} else {
showStatus(`Connection failed: ${data.message}`, 'error');
}
} catch (error) {
showStatus(`Cannot connect to Hyperion: ${error.message}`, 'error');
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', initializeLedPage);