/** * bluetooth.js — Bluetooth scan, pair, MIDI management for Pi Multi-FX Pedal. * * Relies on apiGet, apiPost from app.js and the pedalWS singleton. */ /* ── BT Status ─────────────────────────────────────────────────── */ async function refreshBTStatus() { try { const status = await apiGet('/bluetooth/status'); const powerBtn = document.getElementById('bt-power-btn'); const discBtn = document.getElementById('bt-discoverable-btn'); const nameEl = document.getElementById('bt-adapter-name'); const addrEl = document.getElementById('bt-address'); if (powerBtn) { powerBtn.textContent = status.powered ? 'ON' : 'OFF'; powerBtn.classList.toggle('active', status.powered); } if (discBtn) { discBtn.textContent = status.discoverable ? 'ON' : 'OFF'; discBtn.classList.toggle('active', status.discoverable); } if (nameEl) nameEl.textContent = status.name || '—'; if (addrEl) addrEl.textContent = status.address || '—'; } catch (e) { console.warn('Failed to get BT status:', e); } } /* ── BT Power ──────────────────────────────────────────────────── */ async function toggleBTPower() { const btn = document.getElementById('bt-power-btn'); const current = btn.classList.contains('active'); const newState = !current; btn.disabled = true; try { await apiPost('/bluetooth/power', { on: newState }); refreshBTStatus(); } catch (e) { console.warn('BT power toggle failed:', e); } finally { btn.disabled = false; } } /* ── BT Discoverable ───────────────────────────────────────────── */ async function toggleBTDiscoverable() { const btn = document.getElementById('bt-discoverable-btn'); const current = btn.classList.contains('active'); const newState = !current; btn.disabled = true; try { await apiPost('/bluetooth/discoverable', { on: newState }); refreshBTStatus(); } catch (e) { console.warn('BT discoverable toggle failed:', e); } finally { btn.disabled = false; } } /* ── BT Scan ───────────────────────────────────────────────────── */ async function scanBT() { const btn = document.getElementById('bt-scan-btn'); const results = document.getElementById('bt-scan-results'); btn.disabled = true; btn.textContent = 'Scanning (12s)...'; results.innerHTML = '
Scanning for Bluetooth devices... (12 seconds)
'; try { const data = await apiPost('/bluetooth/scan'); const devices = data.devices || []; if (devices.length === 0) { results.innerHTML = '
No devices found. Make sure devices are discoverable.
'; return; } let html = '
'; for (const dev of devices) { const rssiStr = dev.rssi !== null ? `${dev.rssi} dBm` : '—'; const strengthClass = dev.rssi !== null && dev.rssi > -60 ? 'signal-strong' : dev.rssi !== null && dev.rssi > -80 ? 'signal-medium' : 'signal-weak'; html += `
🔵
${escapeHtml(dev.name)}
${dev.address} ${rssiStr}
`; } html += '
'; results.innerHTML = html; } catch (e) { console.warn('BT scan failed:', e); results.innerHTML = '
Scan failed.
'; } finally { btn.disabled = false; btn.textContent = 'Scan'; } } /* ── Pair / Unpair ─────────────────────────────────────────────── */ async function pairBT(address) { try { const result = await apiPost('/bluetooth/pair', { address }); if (result.ok) { loadPairedDevices(); // Refresh scan results to show paired // alert('Paired successfully!'); } else { alert('Pairing failed: ' + (result.error || 'Unknown error')); } } catch (e) { alert('Network error: ' + e.message); } } async function unpairBT(address) { if (!confirm(`Unpair ${address}?`)) return; try { await apiPost('/bluetooth/unpair', { address }); loadPairedDevices(); } catch (e) { console.warn('Unpair failed:', e); } } async function connectBT(address) { try { const result = await apiPost('/bluetooth/connect', { address }); if (result.ok) { loadPairedDevices(); } else { alert('Connection failed: ' + (result.error || 'Unknown error')); } } catch (e) { console.warn('BT connect failed:', e); } } async function disconnectBT(address) { try { await apiPost('/bluetooth/disconnect', { address }); loadPairedDevices(); } catch (e) { console.warn('BT disconnect failed:', e); } } /* ── Paired Devices ────────────────────────────────────────────── */ async function loadPairedDevices() { const list = document.getElementById('bt-paired-list'); try { const data = await apiGet('/bluetooth/paired'); const devices = data.devices || []; if (devices.length === 0) { list.innerHTML = '
No paired devices.
'; return; } let html = '
'; for (const dev of devices) { const statusClass = dev.connected ? 'connected' : 'disconnected'; const statusText = dev.connected ? 'Connected' : 'Disconnected'; html += `
${dev.connected ? '🔗' : '🔘'}
${escapeHtml(dev.name)}
${dev.address} ${statusText}
${dev.connected ? `` : `` }
`; } html += '
'; list.innerHTML = html; } catch (e) { console.warn('Failed to load paired devices:', e); list.innerHTML = '
Failed to load.
'; } } /* ── BT MIDI Service ───────────────────────────────────────────── */ async function refreshBTMIDIStatus() { try { const status = await apiGet('/bluetooth/midi-status'); const indicator = document.getElementById('bt-midi-indicator'); const enableBtn = document.getElementById('bt-midi-enable-btn'); const disableBtn = document.getElementById('bt-midi-disable-btn'); if (indicator) { if (status.running) { indicator.textContent = 'Running'; indicator.className = 'bt-midi-indicator bt-midi-on'; } else if (status.enabled) { indicator.textContent = 'Enabled (not running)'; indicator.className = 'bt-midi-indicator bt-midi-warn'; } else { indicator.textContent = 'Off'; indicator.className = 'bt-midi-indicator bt-midi-off'; } } // Load MIDI devices try { const devData = await apiGet('/bluetooth/midi-devices'); const devEl = document.getElementById('bt-midi-devices'); if (devEl) { const devs = devData.devices || []; if (devs.length > 0) { devEl.innerHTML = devs.map(d => `
${escapeHtml(d.name)} ${d.address}
` ).join(''); } else { devEl.innerHTML = 'None connected'; } } } catch (e) { // MIDI devices are optional } } catch (e) { console.warn('Failed to get BT MIDI status:', e); } } async function enableBTMIDI() { const btn = document.getElementById('bt-midi-enable-btn'); btn.disabled = true; btn.textContent = 'Enabling...'; try { await apiPost('/bluetooth/midi-enable'); refreshBTMIDIStatus(); } catch (e) { alert('Failed: ' + e.message); } finally { btn.disabled = false; btn.textContent = 'Enable'; } } async function disableBTMIDI() { const btn = document.getElementById('bt-midi-disable-btn'); btn.disabled = true; btn.textContent = 'Disabling...'; try { await apiPost('/bluetooth/midi-disable'); refreshBTMIDIStatus(); } catch (e) { alert('Failed: ' + e.message); } finally { btn.disabled = false; btn.textContent = 'Disable'; } } /* ── Helpers ────────────────────────────────────────────────────── */ function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } /* ── WebSocket listeners ────────────────────────────────────────── */ pedalWS.on('bluetooth_changed', (msg) => { refreshBTStatus(); refreshBTMIDIStatus(); });