// ESP32 SIP Phone - Web Interface (function() { 'use strict'; // State let currentTab = 'status'; let statusUpdateInterval = null; // DOM Elements const elements = { // Status bar wifiStatus: document.getElementById('wifi-status'), sipStatus: document.getElementById('sip-status'), audioStatus: document.getElementById('audio-status'), // Call info callState: document.getElementById('call-state'), callRemote: document.getElementById('call-remote'), callDuration: document.getElementById('call-duration'), callButtons: document.getElementById('call-buttons'), btnAnswer: document.getElementById('btn-answer'), btnReject: document.getElementById('btn-reject'), btnHangup: document.getElementById('btn-hangup'), // Audio audioSource: document.getElementById('audio-source'), usbConnected: document.getElementById('usb-connected'), btConnected: document.getElementById('bt-connected'), volumeSlider: document.getElementById('volume-slider'), volumeValue: document.getElementById('volume-value'), muteCheckbox: document.getElementById('mute-checkbox'), // WiFi wifiForm: document.getElementById('wifi-form'), wifiSsid: document.getElementById('wifi-ssid'), wifiPassword: document.getElementById('wifi-password'), btnScanWifi: document.getElementById('btn-scan-wifi'), wifiScanResults: document.getElementById('wifi-scan-results'), staticIpConfig: document.getElementById('static-ip-config'), // SIP sipForm: document.getElementById('sip-form'), sipServer: document.getElementById('sip-server'), sipPort: document.getElementById('sip-port'), sipUsername: document.getElementById('sip-username'), sipPassword: document.getElementById('sip-password'), sipDisplayName: document.getElementById('sip-display-name'), // Bluetooth btnScanBt: document.getElementById('btn-scan-bt'), btPairedDevices: document.getElementById('bt-paired-devices'), btFoundDevices: document.getElementById('bt-found-devices'), // System btnReboot: document.getElementById('btn-reboot'), btnFactoryReset: document.getElementById('btn-factory-reset') }; // API Functions async function apiGet(endpoint) { try { const response = await fetch('/api/' + endpoint); return await response.json(); } catch (error) { console.error('API Error:', error); return null; } } async function apiPost(endpoint, data = {}) { try { const response = await fetch('/api/' + endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return await response.json(); } catch (error) { console.error('API Error:', error); return null; } } // Status Updates async function updateStatus() { const status = await apiGet('status'); if (!status) return; // WiFi if (status.wifi) { const wifiState = status.wifi.state === 'connected' ? 'Verbunden' : status.wifi.state === 'hotspot' ? 'Hotspot' : 'Getrennt'; elements.wifiStatus.textContent = 'WiFi: ' + wifiState; elements.wifiStatus.className = 'status-item ' + (status.wifi.state === 'connected' ? 'status-connected' : 'status-disconnected'); } // SIP if (status.sip) { const sipState = status.sip.state === 'registered' ? 'Registriert' : status.sip.state === 'registering' ? 'Verbinde...' : 'Nicht registriert'; elements.sipStatus.textContent = 'SIP: ' + sipState; elements.sipStatus.className = 'status-item ' + (status.sip.state === 'registered' ? 'status-connected' : status.sip.state === 'registering' ? 'status-pending' : 'status-disconnected'); } // Audio if (status.audio) { const source = status.audio.source === 'usb' ? 'USB' : status.audio.source === 'bluetooth' ? 'Bluetooth' : 'Keine'; elements.audioStatus.textContent = 'Audio: ' + source; elements.audioSource.textContent = source; elements.usbConnected.textContent = status.audio.usb_connected ? 'Verbunden' : 'Nicht verbunden'; elements.btConnected.textContent = status.audio.bt_connected ? 'Verbunden' : 'Nicht verbunden'; elements.volumeSlider.value = status.audio.volume; elements.volumeValue.textContent = status.audio.volume + '%'; elements.muteCheckbox.checked = status.audio.muted; } // Call if (status.call) { updateCallUI(status.call); } } function updateCallUI(call) { const states = { 'idle': 'Kein aktiver Anruf', 'incoming': 'Eingehender Anruf', 'outgoing': 'Ausgehender Anruf', 'ringing': 'Klingelt...', 'connected': 'Verbunden' }; elements.callState.textContent = states[call.state] || call.state; if (call.state !== 'idle') { elements.callRemote.textContent = (call.name || '') + ' ' + (call.remote || ''); if (call.state === 'connected' && call.duration !== undefined) { const mins = Math.floor(call.duration / 60); const secs = call.duration % 60; elements.callDuration.textContent = mins + ':' + (secs < 10 ? '0' : '') + secs; } else { elements.callDuration.textContent = ''; } elements.callButtons.classList.remove('hidden'); // Show appropriate buttons elements.btnAnswer.classList.toggle('hidden', call.state !== 'incoming'); elements.btnReject.classList.toggle('hidden', call.state !== 'incoming'); elements.btnHangup.classList.toggle('hidden', call.state === 'idle' || call.state === 'incoming'); } else { elements.callRemote.textContent = ''; elements.callDuration.textContent = ''; elements.callButtons.classList.add('hidden'); } } // Tab Navigation function setupTabs() { document.querySelectorAll('.nav-btn').forEach(btn => { btn.addEventListener('click', () => { const tab = btn.dataset.tab; if (tab === currentTab) return; // Update nav document.querySelector('.nav-btn.active').classList.remove('active'); btn.classList.add('active'); // Update content document.querySelector('.tab-content.active').classList.remove('active'); document.getElementById('tab-' + tab).classList.add('active'); currentTab = tab; // Load tab-specific data if (tab === 'wifi') loadWifiConfig(); if (tab === 'sip') loadSipConfig(); if (tab === 'bluetooth') loadBluetoothDevices(); }); }); } // WiFi async function loadWifiConfig() { const config = await apiGet('wifi/config'); if (!config) return; elements.wifiSsid.value = config.ssid || ''; elements.wifiPassword.value = ''; const ipMode = config.ip_mode || 'dhcp'; document.querySelector('input[name="ip-mode"][value="' + ipMode + '"]').checked = true; elements.staticIpConfig.classList.toggle('hidden', ipMode !== 'static'); if (ipMode === 'static') { document.getElementById('static-ip').value = config.static_ip || ''; document.getElementById('gateway').value = config.gateway || ''; document.getElementById('netmask').value = config.netmask || ''; document.getElementById('dns').value = config.dns || ''; } } async function scanWifi() { elements.btnScanWifi.disabled = true; elements.btnScanWifi.textContent = 'Scanne...'; elements.wifiScanResults.classList.remove('hidden'); elements.wifiScanResults.innerHTML = '

Scanne...

'; const networks = await apiGet('wifi/scan'); elements.btnScanWifi.disabled = false; elements.btnScanWifi.textContent = 'Scannen'; if (!networks || networks.length === 0) { elements.wifiScanResults.innerHTML = '

Keine Netzwerke gefunden

'; return; } elements.wifiScanResults.innerHTML = networks.map(net => '
' + '' + escapeHtml(net.ssid) + (net.secure ? ' 🔒' : '') + '' + '' + net.rssi + ' dBm' + '
' ).join(''); elements.wifiScanResults.querySelectorAll('.scan-item').forEach(item => { item.addEventListener('click', () => { elements.wifiSsid.value = item.dataset.ssid; elements.wifiScanResults.classList.add('hidden'); elements.wifiPassword.focus(); }); }); } async function saveWifiConfig(e) { e.preventDefault(); const ipMode = document.querySelector('input[name="ip-mode"]:checked').value; const data = { ssid: elements.wifiSsid.value, password: elements.wifiPassword.value, ip_mode: ipMode }; if (ipMode === 'static') { data.static_ip = document.getElementById('static-ip').value; data.gateway = document.getElementById('gateway').value; data.netmask = document.getElementById('netmask').value; data.dns = document.getElementById('dns').value; } const result = await apiPost('wifi/config', data); if (result && result.success) { alert('WiFi-Konfiguration gespeichert. Verbinde...'); } else { alert('Fehler: ' + (result?.error || 'Unbekannt')); } } // SIP async function loadSipConfig() { const config = await apiGet('sip/config'); if (!config) return; elements.sipServer.value = config.server || ''; elements.sipPort.value = config.port || 5060; elements.sipUsername.value = config.username || ''; elements.sipPassword.value = ''; elements.sipDisplayName.value = config.display_name || ''; } async function saveSipConfig(e) { e.preventDefault(); const data = { server: elements.sipServer.value, port: parseInt(elements.sipPort.value) || 5060, username: elements.sipUsername.value, password: elements.sipPassword.value, display_name: elements.sipDisplayName.value }; const result = await apiPost('sip/config', data); if (result && result.success) { alert('SIP-Konfiguration gespeichert. Registriere...'); } else { alert('Fehler: ' + (result?.error || 'Unbekannt')); } } // Bluetooth async function loadBluetoothDevices() { const devices = await apiGet('bluetooth/devices'); if (!devices || devices.length === 0) { elements.btPairedDevices.innerHTML = '

Keine Gerate gepaart

'; } else { elements.btPairedDevices.innerHTML = devices.map(dev => '
' + '
' + '
' + escapeHtml(dev.name || 'Unbekannt') + '
' + '
' + escapeHtml(dev.address) + '
' + '
' + '
' + '' + '' + '
' + '
' ).join(''); elements.btPairedDevices.querySelectorAll('.btn-connect').forEach(btn => { btn.addEventListener('click', () => connectBluetooth(btn.dataset.address)); }); elements.btPairedDevices.querySelectorAll('.btn-unpair').forEach(btn => { btn.addEventListener('click', () => unpairBluetooth(btn.dataset.address)); }); } } async function scanBluetooth() { elements.btnScanBt.disabled = true; elements.btnScanBt.textContent = 'Suche...'; elements.btFoundDevices.innerHTML = '

Suche Gerate...

'; await apiPost('bluetooth/scan'); // Wait for scan to complete setTimeout(async () => { elements.btnScanBt.disabled = false; elements.btnScanBt.textContent = 'Gerate suchen'; // In a full implementation, we'd poll for discovered devices elements.btFoundDevices.innerHTML = '

Suche abgeschlossen. Gerate werden automatisch gepaart wenn sie in den Pairing-Modus gehen.

'; }, 10000); } async function connectBluetooth(address) { const result = await apiPost('bluetooth/connect', { address }); if (result && result.success) { alert('Verbinde...'); } else { alert('Fehler: ' + (result?.error || 'Unbekannt')); } } async function unpairBluetooth(address) { if (!confirm('Gerat wirklich entfernen?')) return; const result = await apiPost('bluetooth/unpair', { address }); if (result && result.success) { loadBluetoothDevices(); } else { alert('Fehler: ' + (result?.error || 'Unbekannt')); } } // Call Actions async function answerCall() { await apiPost('call/answer'); } async function rejectCall() { await apiPost('call/reject'); } async function hangupCall() { await apiPost('call/hangup'); } // System async function reboot() { if (!confirm('System wirklich neustarten?')) return; await apiPost('system/reboot'); alert('System startet neu...'); } async function factoryReset() { if (!confirm('ACHTUNG: Alle Einstellungen werden geloscht! Fortfahren?')) return; if (!confirm('Wirklich alle Einstellungen loschen?')) return; await apiPost('system/factory-reset'); alert('Werksreset durchgefuhrt. System startet neu...'); } // Utilities function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // Event Listeners function setupEventListeners() { // Call buttons elements.btnAnswer.addEventListener('click', answerCall); elements.btnReject.addEventListener('click', rejectCall); elements.btnHangup.addEventListener('click', hangupCall); // Volume elements.volumeSlider.addEventListener('input', () => { elements.volumeValue.textContent = elements.volumeSlider.value + '%'; }); // WiFi elements.wifiForm.addEventListener('submit', saveWifiConfig); elements.btnScanWifi.addEventListener('click', scanWifi); document.querySelectorAll('input[name="ip-mode"]').forEach(radio => { radio.addEventListener('change', (e) => { elements.staticIpConfig.classList.toggle('hidden', e.target.value !== 'static'); }); }); // SIP elements.sipForm.addEventListener('submit', saveSipConfig); // Bluetooth elements.btnScanBt.addEventListener('click', scanBluetooth); // System elements.btnReboot.addEventListener('click', reboot); elements.btnFactoryReset.addEventListener('click', factoryReset); } // Initialize function init() { setupTabs(); setupEventListeners(); updateStatus(); // Update status every 2 seconds statusUpdateInterval = setInterval(updateStatus, 2000); } // Start if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();