1
0

led-control.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. // LED Control Page - Unified interface for WLED and DW LEDs
  2. let ledConfig = null;
  3. // Utility function to show status messages
  4. function showStatus(message, type = 'info') {
  5. const statusDiv = document.getElementById('dw-leds-status');
  6. if (!statusDiv) return;
  7. const iconMap = {
  8. 'success': 'check_circle',
  9. 'error': 'error',
  10. 'warning': 'warning',
  11. 'info': 'info'
  12. };
  13. const colorMap = {
  14. 'success': 'text-green-700 bg-green-50 border-green-200',
  15. 'error': 'text-red-700 bg-red-50 border-red-200',
  16. 'warning': 'text-amber-700 bg-amber-50 border-amber-200',
  17. 'info': 'text-gray-700 bg-gray-100 border-slate-200'
  18. };
  19. const icon = iconMap[type] || 'info';
  20. const colorClass = colorMap[type] || colorMap.info;
  21. statusDiv.className = `p-4 rounded-lg border ${colorClass}`;
  22. statusDiv.innerHTML = `
  23. <div class="flex items-center gap-2">
  24. <span class="material-icons">${icon}</span>
  25. <span class="text-sm">${message}</span>
  26. </div>
  27. `;
  28. }
  29. // Initialize the page based on LED configuration
  30. async function initializeLedPage() {
  31. try {
  32. const response = await fetch('/get_led_config');
  33. if (!response.ok) throw new Error('Failed to fetch LED config');
  34. ledConfig = await response.json();
  35. const notConfigured = document.getElementById('led-not-configured');
  36. const wledContainer = document.getElementById('wled-container');
  37. const dwLedsContainer = document.getElementById('dw-leds-container');
  38. // Hide all containers first
  39. notConfigured.classList.add('hidden');
  40. wledContainer.classList.add('hidden');
  41. dwLedsContainer.classList.add('hidden');
  42. if (ledConfig.provider === 'wled' && ledConfig.wled_ip) {
  43. // Show WLED iframe
  44. wledContainer.classList.remove('hidden');
  45. const wledFrame = document.getElementById('wled-frame');
  46. if (wledFrame) {
  47. wledFrame.src = `http://${ledConfig.wled_ip}`;
  48. }
  49. } else if (ledConfig.provider === 'dw_leds') {
  50. // Show DW LEDs controls
  51. dwLedsContainer.classList.remove('hidden');
  52. await initializeDWLedsControls();
  53. } else {
  54. // Show not configured message
  55. notConfigured.classList.remove('hidden');
  56. }
  57. } catch (error) {
  58. console.error('Error initializing LED page:', error);
  59. document.getElementById('led-not-configured').classList.remove('hidden');
  60. }
  61. }
  62. // Initialize DW LEDs controls
  63. async function initializeDWLedsControls() {
  64. // Check status and load available effects/palettes
  65. await checkDWLedsStatus();
  66. await loadEffectsAndPalettes();
  67. // Power toggle button
  68. document.getElementById('dw-leds-power-toggle')?.addEventListener('click', async () => {
  69. try {
  70. const response = await fetch('/api/dw_leds/power', {
  71. method: 'POST',
  72. headers: { 'Content-Type': 'application/json' },
  73. body: JSON.stringify({ state: 2 }) // Toggle
  74. });
  75. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  76. const data = await response.json();
  77. if (data.connected) {
  78. showStatus(`Power ${data.power_on ? 'ON' : 'OFF'}`, 'success');
  79. await checkDWLedsStatus();
  80. } else {
  81. showStatus(data.error || 'Failed to toggle power', 'error');
  82. }
  83. } catch (error) {
  84. showStatus(`Failed to toggle power: ${error.message}`, 'error');
  85. }
  86. });
  87. // Brightness slider
  88. const brightnessSlider = document.getElementById('dw-leds-brightness');
  89. const brightnessValue = document.getElementById('dw-leds-brightness-value');
  90. brightnessSlider?.addEventListener('input', (e) => {
  91. brightnessValue.textContent = `${e.target.value}%`;
  92. });
  93. brightnessSlider?.addEventListener('change', async (e) => {
  94. try {
  95. const response = await fetch('/api/dw_leds/brightness', {
  96. method: 'POST',
  97. headers: { 'Content-Type': 'application/json' },
  98. body: JSON.stringify({ value: parseInt(e.target.value) })
  99. });
  100. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  101. const data = await response.json();
  102. if (data.connected) {
  103. showStatus(`Brightness set to ${e.target.value}%`, 'success');
  104. } else {
  105. showStatus(data.error || 'Failed to set brightness', 'error');
  106. }
  107. } catch (error) {
  108. showStatus(`Failed to set brightness: ${error.message}`, 'error');
  109. }
  110. });
  111. // Effect color pickers - apply immediately on change
  112. document.querySelectorAll('.effect-color-picker').forEach(picker => {
  113. picker.addEventListener('change', async () => {
  114. const color1 = document.getElementById('dw-leds-color1')?.value;
  115. const color2 = document.getElementById('dw-leds-color2')?.value;
  116. const color3 = document.getElementById('dw-leds-color3')?.value;
  117. if (color1 && color2 && color3) {
  118. await applyAllColors(color1, color2, color3);
  119. }
  120. });
  121. });
  122. // Effect selector
  123. document.getElementById('dw-leds-effect-select')?.addEventListener('change', async (e) => {
  124. const effectId = parseInt(e.target.value);
  125. if (isNaN(effectId)) return;
  126. try {
  127. const response = await fetch('/api/dw_leds/effect', {
  128. method: 'POST',
  129. headers: { 'Content-Type': 'application/json' },
  130. body: JSON.stringify({ effect_id: effectId })
  131. });
  132. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  133. const data = await response.json();
  134. if (data.connected) {
  135. showStatus(`Effect changed`, 'success');
  136. // Update power button state if backend auto-powered on
  137. if (data.power_on !== undefined) {
  138. updatePowerButtonUI(data.power_on);
  139. }
  140. } else {
  141. showStatus(data.error || 'Failed to set effect', 'error');
  142. }
  143. } catch (error) {
  144. showStatus(`Failed to set effect: ${error.message}`, 'error');
  145. }
  146. });
  147. // Palette selector
  148. document.getElementById('dw-leds-palette-select')?.addEventListener('change', async (e) => {
  149. const paletteId = parseInt(e.target.value);
  150. if (isNaN(paletteId)) return;
  151. try {
  152. const response = await fetch('/api/dw_leds/palette', {
  153. method: 'POST',
  154. headers: { 'Content-Type': 'application/json' },
  155. body: JSON.stringify({ palette_id: paletteId })
  156. });
  157. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  158. const data = await response.json();
  159. if (data.connected) {
  160. showStatus(`Palette changed`, 'success');
  161. // Update power button state if backend auto-powered on
  162. if (data.power_on !== undefined) {
  163. updatePowerButtonUI(data.power_on);
  164. }
  165. } else {
  166. showStatus(data.error || 'Failed to set palette', 'error');
  167. }
  168. } catch (error) {
  169. showStatus(`Failed to set palette: ${error.message}`, 'error');
  170. }
  171. });
  172. // Speed slider
  173. const speedSlider = document.getElementById('dw-leds-speed');
  174. const speedValue = document.getElementById('dw-leds-speed-value');
  175. speedSlider?.addEventListener('input', (e) => {
  176. speedValue.textContent = e.target.value;
  177. });
  178. speedSlider?.addEventListener('change', async (e) => {
  179. try {
  180. const response = await fetch('/api/dw_leds/speed', {
  181. method: 'POST',
  182. headers: { 'Content-Type': 'application/json' },
  183. body: JSON.stringify({ speed: parseInt(e.target.value) })
  184. });
  185. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  186. const data = await response.json();
  187. if (data.connected) {
  188. showStatus(`Speed updated`, 'success');
  189. } else {
  190. showStatus(data.error || 'Failed to set speed', 'error');
  191. }
  192. } catch (error) {
  193. showStatus(`Failed to set speed: ${error.message}`, 'error');
  194. }
  195. });
  196. // Intensity slider
  197. const intensitySlider = document.getElementById('dw-leds-intensity');
  198. const intensityValue = document.getElementById('dw-leds-intensity-value');
  199. intensitySlider?.addEventListener('input', (e) => {
  200. intensityValue.textContent = e.target.value;
  201. });
  202. intensitySlider?.addEventListener('change', async (e) => {
  203. try {
  204. const response = await fetch('/api/dw_leds/intensity', {
  205. method: 'POST',
  206. headers: { 'Content-Type': 'application/json' },
  207. body: JSON.stringify({ intensity: parseInt(e.target.value) })
  208. });
  209. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  210. const data = await response.json();
  211. if (data.connected) {
  212. showStatus(`Intensity updated`, 'success');
  213. } else {
  214. showStatus(data.error || 'Failed to set intensity', 'error');
  215. }
  216. } catch (error) {
  217. showStatus(`Failed to set intensity: ${error.message}`, 'error');
  218. }
  219. });
  220. // Save Current Idle Effect
  221. document.getElementById('dw-leds-save-current-idle')?.addEventListener('click', async () => {
  222. await saveCurrentEffectSettings('idle');
  223. });
  224. // Clear Idle Effect
  225. document.getElementById('dw-leds-clear-idle')?.addEventListener('click', async () => {
  226. await clearEffectSettings('idle');
  227. });
  228. // Save Current Playing Effect
  229. document.getElementById('dw-leds-save-current-playing')?.addEventListener('click', async () => {
  230. await saveCurrentEffectSettings('playing');
  231. });
  232. // Clear Playing Effect
  233. document.getElementById('dw-leds-clear-playing')?.addEventListener('click', async () => {
  234. await clearEffectSettings('playing');
  235. });
  236. // Load and display saved effect settings
  237. await loadEffectSettings();
  238. // Idle timeout controls
  239. await loadIdleTimeout();
  240. const idleTimeoutEnabled = document.getElementById('dw-leds-idle-timeout-enabled');
  241. const idleTimeoutSettings = document.getElementById('idle-timeout-settings');
  242. const idleTimeoutDisabledHelp = document.getElementById('idle-timeout-disabled-help');
  243. // Toggle idle timeout settings visibility and help text
  244. idleTimeoutEnabled?.addEventListener('change', (e) => {
  245. const isEnabled = e.target.checked;
  246. if (isEnabled) {
  247. idleTimeoutSettings?.classList.remove('opacity-50', 'pointer-events-none');
  248. idleTimeoutDisabledHelp?.classList.add('hidden');
  249. } else {
  250. idleTimeoutSettings?.classList.add('opacity-50', 'pointer-events-none');
  251. idleTimeoutDisabledHelp?.classList.remove('hidden');
  252. }
  253. // Auto-save when toggle changes for better UX
  254. saveIdleTimeout();
  255. });
  256. // Save idle timeout settings
  257. document.getElementById('dw-leds-save-idle-timeout')?.addEventListener('click', async () => {
  258. await saveIdleTimeout();
  259. });
  260. // Update remaining time periodically
  261. let idleTimeoutInterval = setInterval(updateIdleTimeoutRemaining, 60000); // Update every minute
  262. // Clean up interval when page unloads
  263. window.addEventListener('beforeunload', () => {
  264. if (idleTimeoutInterval) {
  265. clearInterval(idleTimeoutInterval);
  266. idleTimeoutInterval = null;
  267. }
  268. });
  269. // Initialize Coloris color picker for effect colors
  270. initializeColoris();
  271. }
  272. // Save current LED settings as idle or playing effect
  273. async function saveCurrentEffectSettings(type) {
  274. try {
  275. const effectId = parseInt(document.getElementById('dw-leds-effect-select')?.value) || 0;
  276. const paletteId = parseInt(document.getElementById('dw-leds-palette-select')?.value) || 0;
  277. const speed = parseInt(document.getElementById('dw-leds-speed')?.value) || 128;
  278. const intensity = parseInt(document.getElementById('dw-leds-intensity')?.value) || 128;
  279. // Get effect colors
  280. const color1 = document.getElementById('dw-leds-color1')?.value || '#ff0000';
  281. const color2 = document.getElementById('dw-leds-color2')?.value || '#000000';
  282. const color3 = document.getElementById('dw-leds-color3')?.value || '#0000ff';
  283. const settings = {
  284. type: type, // 'idle' or 'playing'
  285. effect_id: effectId,
  286. palette_id: paletteId,
  287. speed: speed,
  288. intensity: intensity,
  289. color1: color1,
  290. color2: color2,
  291. color3: color3
  292. };
  293. const response = await fetch('/api/dw_leds/save_effect_settings', {
  294. method: 'POST',
  295. headers: { 'Content-Type': 'application/json' },
  296. body: JSON.stringify(settings)
  297. });
  298. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  299. await response.json();
  300. showStatus(`${type.charAt(0).toUpperCase() + type.slice(1)} effect settings saved successfully`, 'success');
  301. // Refresh display
  302. await loadEffectSettings();
  303. } catch (error) {
  304. showStatus(`Failed to save ${type} effect settings: ${error.message}`, 'error');
  305. }
  306. }
  307. // Clear effect settings
  308. async function clearEffectSettings(type) {
  309. try {
  310. const response = await fetch('/api/dw_leds/clear_effect_settings', {
  311. method: 'POST',
  312. headers: { 'Content-Type': 'application/json' },
  313. body: JSON.stringify({ type: type })
  314. });
  315. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  316. showStatus(`${type.charAt(0).toUpperCase() + type.slice(1)} effect cleared`, 'success');
  317. // Refresh display
  318. await loadEffectSettings();
  319. } catch (error) {
  320. showStatus(`Failed to clear ${type} effect: ${error.message}`, 'error');
  321. }
  322. }
  323. // Load and display saved effect settings
  324. async function loadEffectSettings() {
  325. try {
  326. const response = await fetch('/api/dw_leds/get_effect_settings');
  327. if (!response.ok) return;
  328. const data = await response.json();
  329. // Display idle settings
  330. const idleDisplay = document.getElementById('idle-settings-display');
  331. if (idleDisplay) {
  332. idleDisplay.textContent = formatEffectSettings(data.idle_effect);
  333. }
  334. // Display playing settings
  335. const playingDisplay = document.getElementById('playing-settings-display');
  336. if (playingDisplay) {
  337. playingDisplay.textContent = formatEffectSettings(data.playing_effect);
  338. }
  339. } catch (error) {
  340. console.error('Failed to load effect settings:', error);
  341. }
  342. }
  343. // Format effect settings for display
  344. function formatEffectSettings(settings) {
  345. if (!settings) {
  346. return 'Not configured (do nothing)';
  347. }
  348. const parts = [];
  349. // Get effect name from select (if available)
  350. const effectSelect = document.getElementById('dw-leds-effect-select');
  351. if (effectSelect && settings.effect_id !== undefined) {
  352. const effectOption = effectSelect.querySelector(`option[value="${settings.effect_id}"]`);
  353. parts.push(`Effect: ${effectOption ? effectOption.textContent : settings.effect_id}`);
  354. }
  355. // Get palette name from select (if available)
  356. const paletteSelect = document.getElementById('dw-leds-palette-select');
  357. if (paletteSelect && settings.palette_id !== undefined) {
  358. const paletteOption = paletteSelect.querySelector(`option[value="${settings.palette_id}"]`);
  359. parts.push(`Palette: ${paletteOption ? paletteOption.textContent : settings.palette_id}`);
  360. }
  361. if (settings.speed !== undefined) {
  362. parts.push(`Speed: ${settings.speed}`);
  363. }
  364. if (settings.intensity !== undefined) {
  365. parts.push(`Intensity: ${settings.intensity}`);
  366. }
  367. if (settings.color1) {
  368. parts.push(`Colors: ${settings.color1}, ${settings.color2 || '#000000'}, ${settings.color3 || '#0000ff'}`);
  369. }
  370. return parts.join(' | ');
  371. }
  372. // Load idle timeout settings
  373. async function loadIdleTimeout() {
  374. try {
  375. const response = await fetch('/api/dw_leds/idle_timeout');
  376. if (!response.ok) return;
  377. const data = await response.json();
  378. const enabledCheckbox = document.getElementById('dw-leds-idle-timeout-enabled');
  379. const minutesInput = document.getElementById('dw-leds-idle-timeout-minutes');
  380. const idleTimeoutSettings = document.getElementById('idle-timeout-settings');
  381. const idleTimeoutDisabledHelp = document.getElementById('idle-timeout-disabled-help');
  382. if (enabledCheckbox) {
  383. enabledCheckbox.checked = data.enabled;
  384. }
  385. if (minutesInput) {
  386. minutesInput.value = data.minutes;
  387. }
  388. // Set initial state of settings panel and help text
  389. if (data.enabled) {
  390. idleTimeoutSettings?.classList.remove('opacity-50', 'pointer-events-none');
  391. idleTimeoutDisabledHelp?.classList.add('hidden');
  392. } else {
  393. idleTimeoutSettings?.classList.add('opacity-50', 'pointer-events-none');
  394. idleTimeoutDisabledHelp?.classList.remove('hidden');
  395. }
  396. // Update remaining time display
  397. updateIdleTimeoutRemainingDisplay(data.remaining_minutes);
  398. } catch (error) {
  399. console.error('Failed to load idle timeout settings:', error);
  400. }
  401. }
  402. // Save idle timeout settings
  403. async function saveIdleTimeout() {
  404. try {
  405. const enabled = document.getElementById('dw-leds-idle-timeout-enabled')?.checked || false;
  406. const minutes = parseInt(document.getElementById('dw-leds-idle-timeout-minutes')?.value) || 30;
  407. const response = await fetch('/api/dw_leds/idle_timeout', {
  408. method: 'POST',
  409. headers: { 'Content-Type': 'application/json' },
  410. body: JSON.stringify({ enabled, minutes })
  411. });
  412. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  413. const data = await response.json();
  414. if (data.success) {
  415. showStatus(`Idle timeout ${enabled ? 'enabled' : 'disabled'} (${minutes} minutes)`, 'success');
  416. await loadIdleTimeout(); // Reload to get updated remaining time
  417. } else {
  418. showStatus('Failed to save idle timeout settings', 'error');
  419. }
  420. } catch (error) {
  421. showStatus(`Failed to save idle timeout: ${error.message}`, 'error');
  422. }
  423. }
  424. // Update idle timeout remaining time
  425. async function updateIdleTimeoutRemaining() {
  426. try {
  427. const response = await fetch('/api/dw_leds/idle_timeout');
  428. if (!response.ok) return;
  429. const data = await response.json();
  430. updateIdleTimeoutRemainingDisplay(data.remaining_minutes);
  431. } catch (error) {
  432. console.error('Failed to update idle timeout remaining:', error);
  433. }
  434. }
  435. // Update idle timeout remaining time display
  436. function updateIdleTimeoutRemainingDisplay(remainingMinutes) {
  437. const remainingDiv = document.getElementById('idle-timeout-remaining');
  438. const remainingDisplay = document.getElementById('idle-timeout-remaining-display');
  439. if (!remainingDiv || !remainingDisplay) return;
  440. if (remainingMinutes !== null && remainingMinutes !== undefined) {
  441. remainingDiv.classList.remove('hidden');
  442. if (remainingMinutes <= 0) {
  443. remainingDisplay.textContent = 'Timeout expired - LEDs will turn off';
  444. } else if (remainingMinutes < 1) {
  445. remainingDisplay.textContent = 'Less than 1 minute';
  446. } else {
  447. const hours = Math.floor(remainingMinutes / 60);
  448. const mins = Math.round(remainingMinutes % 60);
  449. if (hours > 0) {
  450. remainingDisplay.textContent = `${hours}h ${mins}m`;
  451. } else {
  452. remainingDisplay.textContent = `${mins} minutes`;
  453. }
  454. }
  455. } else {
  456. remainingDiv.classList.add('hidden');
  457. }
  458. }
  459. // Helper function to apply all effect colors
  460. async function applyAllColors(hexColor1, hexColor2, hexColor3) {
  461. try {
  462. const payload = {};
  463. if (hexColor1) {
  464. const r = parseInt(hexColor1.slice(1, 3), 16);
  465. const g = parseInt(hexColor1.slice(3, 5), 16);
  466. const b = parseInt(hexColor1.slice(5, 7), 16);
  467. payload.color1 = [r, g, b];
  468. }
  469. if (hexColor2) {
  470. const r = parseInt(hexColor2.slice(1, 3), 16);
  471. const g = parseInt(hexColor2.slice(3, 5), 16);
  472. const b = parseInt(hexColor2.slice(5, 7), 16);
  473. payload.color2 = [r, g, b];
  474. }
  475. if (hexColor3) {
  476. const r = parseInt(hexColor3.slice(1, 3), 16);
  477. const g = parseInt(hexColor3.slice(3, 5), 16);
  478. const b = parseInt(hexColor3.slice(5, 7), 16);
  479. payload.color3 = [r, g, b];
  480. }
  481. const response = await fetch('/api/dw_leds/colors', {
  482. method: 'POST',
  483. headers: { 'Content-Type': 'application/json' },
  484. body: JSON.stringify(payload)
  485. });
  486. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  487. const data = await response.json();
  488. if (data.connected) {
  489. showStatus(`Effect colors updated`, 'success');
  490. } else {
  491. showStatus(data.error || 'Failed to set colors', 'error');
  492. }
  493. } catch (error) {
  494. showStatus(`Failed to set colors: ${error.message}`, 'error');
  495. }
  496. }
  497. // Load available effects and palettes
  498. async function loadEffectsAndPalettes() {
  499. try {
  500. // Load effects
  501. const effectsResponse = await fetch('/api/dw_leds/effects');
  502. if (effectsResponse.ok) {
  503. const effectsData = await effectsResponse.json();
  504. const effectSelect = document.getElementById('dw-leds-effect-select');
  505. const idleEffectSelect = document.getElementById('dw-leds-idle-effect');
  506. const playingEffectSelect = document.getElementById('dw-leds-playing-effect');
  507. if (effectSelect && effectsData.effects) {
  508. effectSelect.innerHTML = '';
  509. // Sort effects alphabetically by name
  510. const sortedEffects = [...effectsData.effects].sort((a, b) =>
  511. a[1].localeCompare(b[1])
  512. );
  513. sortedEffects.forEach(([id, name]) => {
  514. const option = document.createElement('option');
  515. option.value = id;
  516. option.textContent = name;
  517. effectSelect.appendChild(option);
  518. });
  519. }
  520. // Add effects to automation selectors
  521. if (idleEffectSelect && effectsData.effects) {
  522. idleEffectSelect.innerHTML = '<option value="off">Off</option>';
  523. // Sort effects alphabetically by name
  524. const sortedEffects = [...effectsData.effects].sort((a, b) =>
  525. a[1].localeCompare(b[1])
  526. );
  527. sortedEffects.forEach(([, name]) => {
  528. const option = document.createElement('option');
  529. option.value = name.toLowerCase();
  530. option.textContent = name;
  531. idleEffectSelect.appendChild(option);
  532. });
  533. }
  534. if (playingEffectSelect && effectsData.effects) {
  535. playingEffectSelect.innerHTML = '<option value="off">Off</option>';
  536. // Sort effects alphabetically by name
  537. const sortedEffects = [...effectsData.effects].sort((a, b) =>
  538. a[1].localeCompare(b[1])
  539. );
  540. sortedEffects.forEach(([, name]) => {
  541. const option = document.createElement('option');
  542. option.value = name.toLowerCase();
  543. option.textContent = name;
  544. playingEffectSelect.appendChild(option);
  545. });
  546. }
  547. // Load saved automation settings
  548. const configResponse = await fetch('/get_led_config');
  549. if (configResponse.ok) {
  550. const config = await configResponse.json();
  551. if (idleEffectSelect && config.dw_led_idle_effect) {
  552. idleEffectSelect.value = config.dw_led_idle_effect;
  553. }
  554. if (playingEffectSelect && config.dw_led_playing_effect) {
  555. playingEffectSelect.value = config.dw_led_playing_effect;
  556. }
  557. }
  558. }
  559. // Load palettes
  560. const palettesResponse = await fetch('/api/dw_leds/palettes');
  561. if (palettesResponse.ok) {
  562. const palettesData = await palettesResponse.json();
  563. const paletteSelect = document.getElementById('dw-leds-palette-select');
  564. if (paletteSelect && palettesData.palettes) {
  565. paletteSelect.innerHTML = '';
  566. // Sort palettes alphabetically by name
  567. const sortedPalettes = [...palettesData.palettes].sort((a, b) =>
  568. a[1].localeCompare(b[1])
  569. );
  570. sortedPalettes.forEach(([id, name]) => {
  571. const option = document.createElement('option');
  572. option.value = id;
  573. option.textContent = name;
  574. paletteSelect.appendChild(option);
  575. });
  576. }
  577. }
  578. } catch (error) {
  579. console.error('Failed to load effects and palettes:', error);
  580. showStatus('Failed to load effects and palettes', 'error');
  581. }
  582. }
  583. // Helper function to update power button UI based on power state
  584. function updatePowerButtonUI(powerOn) {
  585. const powerButton = document.getElementById('dw-leds-power-toggle');
  586. const powerButtonText = document.getElementById('dw-leds-power-text');
  587. if (powerButton && powerButtonText) {
  588. if (powerOn) {
  589. 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';
  590. powerButtonText.textContent = 'Turn OFF';
  591. } else {
  592. 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';
  593. powerButtonText.textContent = 'Turn ON';
  594. }
  595. }
  596. }
  597. // Check DW LEDs connection status
  598. async function checkDWLedsStatus() {
  599. try {
  600. const response = await fetch('/api/dw_leds/status');
  601. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  602. const data = await response.json();
  603. if (data.connected) {
  604. const powerState = data.power_on ? 'ON' : 'OFF';
  605. showStatus(`Connected: ${data.num_leds} LEDs on GPIO ${data.gpio_pin} - Power: ${powerState}`, 'success');
  606. // Update power button appearance
  607. updatePowerButtonUI(data.power_on);
  608. // Update slider values
  609. const brightnessSlider = document.getElementById('dw-leds-brightness');
  610. const brightnessValue = document.getElementById('dw-leds-brightness-value');
  611. if (brightnessSlider && data.brightness !== undefined) {
  612. brightnessSlider.value = data.brightness;
  613. if (brightnessValue) brightnessValue.textContent = `${data.brightness}%`;
  614. }
  615. const speedSlider = document.getElementById('dw-leds-speed');
  616. const speedValue = document.getElementById('dw-leds-speed-value');
  617. if (speedSlider && data.speed !== undefined) {
  618. speedSlider.value = data.speed;
  619. if (speedValue) speedValue.textContent = data.speed;
  620. }
  621. const intensitySlider = document.getElementById('dw-leds-intensity');
  622. const intensityValue = document.getElementById('dw-leds-intensity-value');
  623. if (intensitySlider && data.intensity !== undefined) {
  624. intensitySlider.value = data.intensity;
  625. if (intensityValue) intensityValue.textContent = data.intensity;
  626. }
  627. // Update effect and palette selectors
  628. const effectSelect = document.getElementById('dw-leds-effect-select');
  629. if (effectSelect && data.current_effect !== undefined) {
  630. effectSelect.value = data.current_effect;
  631. }
  632. const paletteSelect = document.getElementById('dw-leds-palette-select');
  633. if (paletteSelect && data.current_palette !== undefined) {
  634. paletteSelect.value = data.current_palette;
  635. }
  636. // Update color pickers if colors are provided
  637. if (data.colors && Array.isArray(data.colors)) {
  638. const color1 = document.getElementById('dw-leds-color1');
  639. const color2 = document.getElementById('dw-leds-color2');
  640. const color3 = document.getElementById('dw-leds-color3');
  641. if (color1 && data.colors[0]) {
  642. color1.value = data.colors[0];
  643. updateColorPickerStyle(color1, data.colors[0]);
  644. }
  645. if (color2 && data.colors[1]) {
  646. color2.value = data.colors[1];
  647. updateColorPickerStyle(color2, data.colors[1]);
  648. }
  649. if (color3 && data.colors[2]) {
  650. color3.value = data.colors[2];
  651. updateColorPickerStyle(color3, data.colors[2]);
  652. }
  653. }
  654. } else {
  655. // Show error message from controller
  656. const errorMsg = data.error || 'Connection failed';
  657. showStatus(errorMsg, 'error');
  658. }
  659. } catch (error) {
  660. showStatus(`Cannot connect to DW LEDs: ${error.message}`, 'error');
  661. }
  662. }
  663. // Helper function to update color picker background
  664. function updateColorPickerStyle(input, color) {
  665. if (!input || !color) return;
  666. input.style.backgroundColor = color;
  667. }
  668. // Initialize Coloris color picker
  669. function initializeColoris() {
  670. // Initialize Coloris with custom configuration
  671. Coloris({
  672. theme: 'polaroid',
  673. themeMode: 'auto',
  674. formatToggle: true,
  675. alpha: false, // No transparency for LED colors
  676. swatches: [
  677. '#ff0000', // Red
  678. '#00ff00', // Green
  679. '#0000ff', // Blue
  680. '#ffff00', // Yellow
  681. '#ff00ff', // Magenta
  682. '#00ffff', // Cyan
  683. '#ff8000', // Orange
  684. '#ffffff', // White
  685. '#2a9d8f', // Teal
  686. '#e9c46a', // Sand
  687. 'coral', // Coral
  688. 'Crimson' // Crimson
  689. ],
  690. onChange: (color, input) => {
  691. // Update the input background to show the selected color
  692. updateColorPickerStyle(input, color);
  693. }
  694. });
  695. // Apply Coloris to all effect color pickers and set initial background colors
  696. const colorPickers = document.querySelectorAll('.effect-color-picker');
  697. colorPickers.forEach(picker => {
  698. picker.setAttribute('data-coloris', '');
  699. // Set initial background color and text color
  700. updateColorPickerStyle(picker, picker.value);
  701. });
  702. }
  703. // Initialize on page load
  704. document.addEventListener('DOMContentLoaded', initializeLedPage);