led-control.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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. // Color picker - update display when color changes
  112. const colorPicker = document.getElementById('dw-leds-color');
  113. const colorHexDisplay = document.getElementById('dw-leds-color-hex');
  114. colorPicker?.addEventListener('input', (e) => {
  115. if (colorHexDisplay) {
  116. colorHexDisplay.textContent = e.target.value.toUpperCase();
  117. }
  118. });
  119. // Color picker - apply button
  120. document.getElementById('dw-leds-set-color')?.addEventListener('click', async () => {
  121. await applyColor(colorPicker.value);
  122. });
  123. // Quick color buttons
  124. document.querySelectorAll('.dw-leds-quick-color').forEach(button => {
  125. button.addEventListener('click', async () => {
  126. const hexColor = button.getAttribute('data-color');
  127. await applyColor(hexColor);
  128. // Update color picker and hex display to match
  129. if (colorPicker) colorPicker.value = hexColor;
  130. if (colorHexDisplay) colorHexDisplay.textContent = hexColor.toUpperCase();
  131. });
  132. });
  133. // Effect selector
  134. document.getElementById('dw-leds-effect-select')?.addEventListener('change', async (e) => {
  135. const effectId = parseInt(e.target.value);
  136. if (isNaN(effectId)) return;
  137. try {
  138. const response = await fetch('/api/dw_leds/effect', {
  139. method: 'POST',
  140. headers: { 'Content-Type': 'application/json' },
  141. body: JSON.stringify({ effect_id: effectId })
  142. });
  143. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  144. const data = await response.json();
  145. if (data.connected) {
  146. showStatus(`Effect changed`, 'success');
  147. } else {
  148. showStatus(data.error || 'Failed to set effect', 'error');
  149. }
  150. } catch (error) {
  151. showStatus(`Failed to set effect: ${error.message}`, 'error');
  152. }
  153. });
  154. // Palette selector
  155. document.getElementById('dw-leds-palette-select')?.addEventListener('change', async (e) => {
  156. const paletteId = parseInt(e.target.value);
  157. if (isNaN(paletteId)) return;
  158. try {
  159. const response = await fetch('/api/dw_leds/palette', {
  160. method: 'POST',
  161. headers: { 'Content-Type': 'application/json' },
  162. body: JSON.stringify({ palette_id: paletteId })
  163. });
  164. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  165. const data = await response.json();
  166. if (data.connected) {
  167. showStatus(`Palette changed`, 'success');
  168. } else {
  169. showStatus(data.error || 'Failed to set palette', 'error');
  170. }
  171. } catch (error) {
  172. showStatus(`Failed to set palette: ${error.message}`, 'error');
  173. }
  174. });
  175. // Speed slider
  176. const speedSlider = document.getElementById('dw-leds-speed');
  177. const speedValue = document.getElementById('dw-leds-speed-value');
  178. speedSlider?.addEventListener('input', (e) => {
  179. speedValue.textContent = e.target.value;
  180. });
  181. speedSlider?.addEventListener('change', async (e) => {
  182. try {
  183. const response = await fetch('/api/dw_leds/speed', {
  184. method: 'POST',
  185. headers: { 'Content-Type': 'application/json' },
  186. body: JSON.stringify({ speed: parseInt(e.target.value) })
  187. });
  188. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  189. const data = await response.json();
  190. if (data.connected) {
  191. showStatus(`Speed updated`, 'success');
  192. } else {
  193. showStatus(data.error || 'Failed to set speed', 'error');
  194. }
  195. } catch (error) {
  196. showStatus(`Failed to set speed: ${error.message}`, 'error');
  197. }
  198. });
  199. // Intensity slider
  200. const intensitySlider = document.getElementById('dw-leds-intensity');
  201. const intensityValue = document.getElementById('dw-leds-intensity-value');
  202. intensitySlider?.addEventListener('input', (e) => {
  203. intensityValue.textContent = e.target.value;
  204. });
  205. intensitySlider?.addEventListener('change', async (e) => {
  206. try {
  207. const response = await fetch('/api/dw_leds/intensity', {
  208. method: 'POST',
  209. headers: { 'Content-Type': 'application/json' },
  210. body: JSON.stringify({ intensity: parseInt(e.target.value) })
  211. });
  212. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  213. const data = await response.json();
  214. if (data.connected) {
  215. showStatus(`Intensity updated`, 'success');
  216. } else {
  217. showStatus(data.error || 'Failed to set intensity', 'error');
  218. }
  219. } catch (error) {
  220. showStatus(`Failed to set intensity: ${error.message}`, 'error');
  221. }
  222. });
  223. // Save automation effects button
  224. document.getElementById('dw-leds-save-effects')?.addEventListener('click', async () => {
  225. try {
  226. const idleEffect = document.getElementById('dw-leds-idle-effect')?.value || 'off';
  227. const playingEffect = document.getElementById('dw-leds-playing-effect')?.value || 'off';
  228. const response = await fetch('/api/dw_leds/set_effects', {
  229. method: 'POST',
  230. headers: { 'Content-Type': 'application/json' },
  231. body: JSON.stringify({
  232. idle_effect: idleEffect,
  233. playing_effect: playingEffect
  234. })
  235. });
  236. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  237. await response.json();
  238. showStatus('Effect settings saved successfully', 'success');
  239. } catch (error) {
  240. showStatus(`Failed to save effect settings: ${error.message}`, 'error');
  241. }
  242. });
  243. }
  244. // Helper function to apply color
  245. async function applyColor(hexColor) {
  246. try {
  247. // Convert hex to RGB
  248. const r = parseInt(hexColor.slice(1, 3), 16);
  249. const g = parseInt(hexColor.slice(3, 5), 16);
  250. const b = parseInt(hexColor.slice(5, 7), 16);
  251. const response = await fetch('/api/dw_leds/color', {
  252. method: 'POST',
  253. headers: { 'Content-Type': 'application/json' },
  254. body: JSON.stringify({ r, g, b })
  255. });
  256. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  257. const data = await response.json();
  258. if (data.connected) {
  259. showStatus(`Color set to ${hexColor.toUpperCase()}`, 'success');
  260. } else {
  261. showStatus(data.error || 'Failed to set color', 'error');
  262. }
  263. } catch (error) {
  264. showStatus(`Failed to set color: ${error.message}`, 'error');
  265. }
  266. }
  267. // Load available effects and palettes
  268. async function loadEffectsAndPalettes() {
  269. try {
  270. // Load effects
  271. const effectsResponse = await fetch('/api/dw_leds/effects');
  272. if (effectsResponse.ok) {
  273. const effectsData = await effectsResponse.json();
  274. const effectSelect = document.getElementById('dw-leds-effect-select');
  275. const idleEffectSelect = document.getElementById('dw-leds-idle-effect');
  276. const playingEffectSelect = document.getElementById('dw-leds-playing-effect');
  277. if (effectSelect && effectsData.effects) {
  278. effectSelect.innerHTML = '';
  279. effectsData.effects.forEach(([id, name]) => {
  280. const option = document.createElement('option');
  281. option.value = id;
  282. option.textContent = name;
  283. effectSelect.appendChild(option);
  284. });
  285. }
  286. // Add effects to automation selectors
  287. if (idleEffectSelect && effectsData.effects) {
  288. idleEffectSelect.innerHTML = '<option value="off">Off</option>';
  289. effectsData.effects.forEach(([id, name]) => {
  290. const option = document.createElement('option');
  291. option.value = name.toLowerCase();
  292. option.textContent = name;
  293. idleEffectSelect.appendChild(option);
  294. });
  295. }
  296. if (playingEffectSelect && effectsData.effects) {
  297. playingEffectSelect.innerHTML = '<option value="off">Off</option>';
  298. effectsData.effects.forEach(([id, name]) => {
  299. const option = document.createElement('option');
  300. option.value = name.toLowerCase();
  301. option.textContent = name;
  302. playingEffectSelect.appendChild(option);
  303. });
  304. }
  305. // Load saved automation settings
  306. const configResponse = await fetch('/get_led_config');
  307. if (configResponse.ok) {
  308. const config = await configResponse.json();
  309. if (idleEffectSelect && config.dw_led_idle_effect) {
  310. idleEffectSelect.value = config.dw_led_idle_effect;
  311. }
  312. if (playingEffectSelect && config.dw_led_playing_effect) {
  313. playingEffectSelect.value = config.dw_led_playing_effect;
  314. }
  315. }
  316. }
  317. // Load palettes
  318. const palettesResponse = await fetch('/api/dw_leds/palettes');
  319. if (palettesResponse.ok) {
  320. const palettesData = await palettesResponse.json();
  321. const paletteSelect = document.getElementById('dw-leds-palette-select');
  322. if (paletteSelect && palettesData.palettes) {
  323. paletteSelect.innerHTML = '';
  324. palettesData.palettes.forEach(([id, name]) => {
  325. const option = document.createElement('option');
  326. option.value = id;
  327. option.textContent = name;
  328. paletteSelect.appendChild(option);
  329. });
  330. }
  331. }
  332. } catch (error) {
  333. console.error('Failed to load effects and palettes:', error);
  334. showStatus('Failed to load effects and palettes', 'error');
  335. }
  336. }
  337. // Check DW LEDs connection status
  338. async function checkDWLedsStatus() {
  339. try {
  340. const response = await fetch('/api/dw_leds/status');
  341. if (!response.ok) throw new Error(`HTTP ${response.status}`);
  342. const data = await response.json();
  343. if (data.connected) {
  344. const powerState = data.power_on ? 'ON' : 'OFF';
  345. showStatus(`Connected: ${data.num_leds} LEDs on GPIO ${data.gpio_pin} - Power: ${powerState}`, 'success');
  346. // Update power button appearance
  347. const powerButton = document.getElementById('dw-leds-power-toggle');
  348. const powerButtonText = document.getElementById('dw-leds-power-text');
  349. if (powerButton && powerButtonText) {
  350. if (data.power_on) {
  351. 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';
  352. powerButtonText.textContent = 'Turn OFF';
  353. } else {
  354. 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';
  355. powerButtonText.textContent = 'Turn ON';
  356. }
  357. }
  358. // Update slider values
  359. const brightnessSlider = document.getElementById('dw-leds-brightness');
  360. const brightnessValue = document.getElementById('dw-leds-brightness-value');
  361. if (brightnessSlider && data.brightness !== undefined) {
  362. brightnessSlider.value = data.brightness;
  363. if (brightnessValue) brightnessValue.textContent = `${data.brightness}%`;
  364. }
  365. const speedSlider = document.getElementById('dw-leds-speed');
  366. const speedValue = document.getElementById('dw-leds-speed-value');
  367. if (speedSlider && data.speed !== undefined) {
  368. speedSlider.value = data.speed;
  369. if (speedValue) speedValue.textContent = data.speed;
  370. }
  371. const intensitySlider = document.getElementById('dw-leds-intensity');
  372. const intensityValue = document.getElementById('dw-leds-intensity-value');
  373. if (intensitySlider && data.intensity !== undefined) {
  374. intensitySlider.value = data.intensity;
  375. if (intensityValue) intensityValue.textContent = data.intensity;
  376. }
  377. // Update effect and palette selectors
  378. const effectSelect = document.getElementById('dw-leds-effect-select');
  379. if (effectSelect && data.current_effect !== undefined) {
  380. effectSelect.value = data.current_effect;
  381. }
  382. const paletteSelect = document.getElementById('dw-leds-palette-select');
  383. if (paletteSelect && data.current_palette !== undefined) {
  384. paletteSelect.value = data.current_palette;
  385. }
  386. } else {
  387. // Show error message from controller
  388. const errorMsg = data.error || 'Connection failed';
  389. showStatus(errorMsg, 'error');
  390. }
  391. } catch (error) {
  392. showStatus(`Cannot connect to DW LEDs: ${error.message}`, 'error');
  393. }
  394. }
  395. // Initialize on page load
  396. document.addEventListener('DOMContentLoaded', initializeLedPage);