/**
* 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();
});