// RadioControl WebUI — Hardware Interface Controller let config = {}; let currentTab = 'radio'; let toastTimer = null; let atHistory = []; // ---- API ---- async function api(path, opts = {}) { try { const res = await fetch(path, opts); return await res.json(); } catch (e) { showToast('Connection error', 'error'); return null; } } const post = (path, body) => api(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); // ---- Toast ---- function showToast(msg, type = 'success') { const el = document.getElementById('toast'); el.textContent = msg; el.className = 'toast ' + type + ' show'; clearTimeout(toastTimer); toastTimer = setTimeout(() => el.className = 'toast', 2500); } // ---- Tabs ---- function switchTab(tab) { currentTab = tab; document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active')); document.getElementById('page-' + tab).classList.add('active'); document.querySelector(`[data-tab="${tab}"]`).classList.add('active'); refreshTab(tab); } async function refreshTab(tab) { switch (tab) { case 'radio': await loadRadio(); await loadCPDebug(); break; case 'terminal': await loadTerminal(); break; case 'wifi': await loadWifi(); break; case 'carrier': await loadCarrier(); break; case 'debug': await loadDebug(); await loadThreadInfo(); break; case 'flags': await loadFlags(); break; } } // ---- Radio page ---- async function loadRadio() { config = await api('/api/status') || config; const radio = await api('/api/radio'); setToggle('toggle-eng', config.engineering_mode); setToggle('toggle-factory', config.factory_test_mode); setToggle('toggle-hidden', config.hidden_menus); setToggle('toggle-diag', config.usb_diag_mode); setToggle('toggle-modem', config.modem_log); setToggle('toggle-stealth', config.stealth_mode); updateChips(); if (radio) { setText('info-soc', radio.soc_family || 'unknown'); setText('info-baseband', radio.baseband || 'N/A'); setText('info-chipset', radio.chipset || 'N/A'); setText('info-network', radio.network_type || 'N/A'); setText('info-operator', radio.operator || 'N/A'); setText('info-sim', radio.sim_state || 'N/A'); setText('soc-badge', `${(radio.soc_family || 'unknown').toUpperCase()} — ${radio.chipset || '?'}`); } // Modem interfaces const ifaces = await api('/api/modem/interfaces'); const mc = document.getElementById('modem-interfaces'); if (ifaces && ifaces.length > 0) { mc.innerHTML = ifaces.map(i => `
${i.path}
${i.perms}
`).join(''); } else { mc.innerHTML = '
No modem interfaces detected
'; } // Thermal const thermal = await api('/api/thermal'); const tc = document.getElementById('thermal-info'); if (thermal && thermal.length > 0) { tc.innerHTML = thermal.map(t => { const tempC = (t.temp / 1000).toFixed(1); const color = t.temp > 60000 ? 'var(--danger)' : t.temp > 45000 ? 'var(--warning)' : 'var(--success)'; return `
${t.type}
${t.zone}
${tempC}°C
`; }).join(''); } else { tc.innerHTML = '
No RF thermal zones
'; } } function setToggle(id, val) { const el = document.getElementById(id); if (el) el.checked = val === '1' || val === 1; } function setText(id, val) { const el = document.getElementById(id); if (el) el.textContent = val; } function updateChips() { setChip('chip-eng', config.engineering_mode === '1'); setChip('chip-factory', config.factory_test_mode === '1'); setChip('chip-wifi', config.wifi_mode !== 'managed' && config.wifi_mode); } function setChip(id, on) { const dot = document.querySelector(`#${id} .dot`); if (dot) dot.className = 'dot ' + (on ? '' : 'off'); } async function handleToggle(key, el) { const val = el.checked ? '1' : '0'; const res = await post('/api/config', { key, value: val }); if (res && res.ok) { config[key.toLowerCase()] = val; updateChips(); showToast('Updated — reboot to apply'); } else { el.checked = !el.checked; showToast('Failed', 'error'); } } async function reboot() { if (confirm('Reboot to apply all changes?')) { await post('/api/reboot', {}); showToast('Rebooting...'); } } // ---- AT Terminal ---- // AT command presets — verified against Shannon 5400 (S5400BUNUELO) AT+CLAC // Pixel 10 Pro Fold / Tensor G5 / laguna const AT_PRESETS = { common: [ { cmd: 'AT', desc: 'Modem alive check' }, { cmd: 'AT+CFUN?', desc: 'Radio function state (0=off, 1=on, 4=airplane)' }, { cmd: 'AT+CFUN=1', desc: 'Turn radio ON' }, { cmd: 'AT+CFUN=0', desc: 'Turn radio OFF' }, { cmd: 'AT+COPS?', desc: 'Current operator' }, { cmd: 'AT+COPS=?', desc: 'Scan all available networks (slow ~30s)' }, { cmd: 'AT+CSQ', desc: 'Signal quality (RSSI, BER)' }, { cmd: 'AT+CESQ', desc: 'Extended signal: RXLEV,BER,RSCP,Ec/No,RSRQ,RSRP,SINR' }, { cmd: 'AT+CREG?', desc: '2G/3G registration status' }, { cmd: 'AT+CEREG?', desc: 'LTE registration status' }, { cmd: 'AT+CGREG?', desc: 'GPRS registration status' }, { cmd: 'AT+CGDCONT?', desc: 'PDP context / APN list' }, { cmd: 'AT+CGPADDR', desc: 'PDP addresses (IP assignments)' }, { cmd: 'AT+CGATT?', desc: 'PS attach status' }, { cmd: 'AT+CPIN?', desc: 'SIM status (READY/PIN/PUK/not inserted)' }, { cmd: 'AT+CGSN', desc: 'IMEI(s)' }, { cmd: 'AT+CIMI', desc: 'IMSI (SIM identity)' }, { cmd: 'AT+CNUM', desc: 'Subscriber number (phone number)' }, { cmd: 'AT+CGMI', desc: 'Manufacturer (Samsung Electronics)' }, { cmd: 'AT+CGMM', desc: 'Model (S5400BUNUELO)' }, { cmd: 'AT+CGMR', desc: 'Firmware revision' }, { cmd: 'AT+CLAC', desc: 'List ALL supported AT commands' }, { cmd: 'AT+WS46?', desc: 'Wireless data service type' }, { cmd: 'AT+CEMODE?', desc: 'UE mode of operation (CS/PS)' }, ], // Shannon 5400 specific commands (verified on device) tensor: [ { cmd: 'AT+SCELL', desc: 'Serving cell info (band, EARFCN, PCI, RSRP)' }, { cmd: 'AT+NCELL', desc: 'Neighbor cell list' }, { cmd: 'AT+RSRP', desc: 'Reference Signal Received Power' }, { cmd: 'AT+RSRQ', desc: 'Reference Signal Received Quality' }, { cmd: 'AT+RSCP', desc: 'Received Signal Code Power (3G)' }, { cmd: 'AT+ECNO', desc: 'Ec/No (3G signal quality)' }, { cmd: 'AT$RSRP', desc: 'Extended RSRP readout' }, { cmd: 'AT$RSRQ', desc: 'Extended RSRQ readout' }, { cmd: 'AT$CSQ', desc: 'Extended signal quality' }, { cmd: 'AT$CREG', desc: 'Extended registration info' }, { cmd: 'AT$ROAM', desc: 'Roaming status/config' }, { cmd: 'AT$ARME', desc: 'ARM Engine — modem subsystem control' }, { cmd: 'AT$ARMEE', desc: 'ARM Engine extended' }, { cmd: 'AT*CNTI', desc: 'Current Network Technology Indicator' }, { cmd: 'AT+NBLTESCAN', desc: 'NB-IoT/LTE cell scan' }, { cmd: 'AT+DEVFEAT', desc: 'Device feature flags' }, { cmd: 'AT+HANDLEDRX', desc: 'DRX (Discontinuous Reception) control' }, { cmd: 'AT+PING', desc: 'Modem-level ping test' }, { cmd: 'AT+PACSP?', desc: 'Preferred ACSP setting' }, { cmd: 'AT+SKYLOTEST', desc: 'Satellite IoT (Skylo/NTN) test mode' }, { cmd: 'AT+CPSMS?', desc: 'Power Saving Mode settings' }, { cmd: 'AT+CCIOTOPT?', desc: 'CIoT optimization config' }, { cmd: 'AT+CGEQOS?', desc: 'EPS QoS parameters' }, { cmd: 'AT+C5GQOS?', desc: '5G QoS parameters' }, { cmd: 'AT+C5GNSSAI?', desc: '5G Network Slice (NSSAI) config' }, { cmd: 'AT+C5GPNSSAI?', desc: '5G Preferred NSSAI' }, { cmd: 'AT+C5GUSMS?', desc: '5G USMS config' }, { cmd: 'AT+CSUPI?', desc: 'SUPI (5G subscriber permanent identity)' }, { cmd: 'AT+CVMOD?', desc: 'Voice mode preference' }, { cmd: 'AT+AIMSCH', desc: 'IMS channel status' }, { cmd: 'AT+AIMSDB', desc: 'IMS debug info' }, { cmd: 'AT+AIMSRX', desc: 'IMS receive status' }, { cmd: 'AT+VZWRSRP', desc: 'Verizon RSRP (carrier-specific signal)' }, { cmd: 'AT+VZWRSRQ', desc: 'Verizon RSRQ' }, { cmd: 'AT+VZWAPNE?', desc: 'Verizon APN entries' }, { cmd: 'AT+SKT', desc: 'SK Telecom specific query' }, { cmd: 'AT+CEER', desc: 'Extended error report (last call/data failure reason)' }, ], // Qualcomm still relevant for other devices qualcomm: [ { cmd: 'AT+QENG="servingcell"', desc: 'Serving cell engineering info' }, { cmd: 'AT+QENG="neighbourcell"', desc: 'Neighbor cell list' }, { cmd: 'AT+QCAINFO', desc: 'Active carrier aggregation status' }, { cmd: 'AT+QNWPREFCFG="mode_pref"', desc: 'Network mode preference' }, { cmd: 'AT+QNWPREFCFG="lte_band"', desc: 'LTE band preference' }, { cmd: 'AT+QNWPREFCFG="nr5g_band"', desc: 'NR band preference' }, { cmd: 'AT+QCFG="band"', desc: 'Band lock config (hex bitmask)' }, { cmd: 'AT+QRSRP', desc: 'Per-antenna RSRP' }, { cmd: 'AT+QMBNCFG="list"', desc: 'MBN carrier config profiles' }, { cmd: 'AT+QNWLOCK="common/4g"', desc: 'Cell lock status' }, { cmd: 'AT+QSCAN=1', desc: 'Quick cell scan' }, ], exynos: [ { cmd: 'AT+SCELL', desc: 'Serving cell info' }, { cmd: 'AT+NCELL', desc: 'Neighbor cells' }, { cmd: 'AT+RSRP', desc: 'RSRP' }, { cmd: 'AT+RSRQ', desc: 'RSRQ' }, { cmd: 'AT$RSRP', desc: 'Extended RSRP' }, { cmd: 'AT$RSRQ', desc: 'Extended RSRQ' }, { cmd: 'AT$ARME', desc: 'ARM Engine control' }, { cmd: 'AT*CNTI', desc: 'Network technology indicator' }, { cmd: 'AT+NBLTESCAN', desc: 'Cell scan' }, { cmd: 'AT+DEVFEAT', desc: 'Device features' }, ], }; // Full Shannon 5400 AT command reference — verified via AT+CLAC on Pixel 10 Pro Fold // Modem: S5400BUNUELO, Firmware: g5400i-251201-260127-B-14784805 // RIL: Samsung S.LSI Vendor RIL 5400 V2.3 const DISCOVERED_COMMANDS = { 'Signal & Cell Info': [ { cmd: 'AT+SCELL', desc: 'Serving cell info — band, EARFCN, PCI, RSRP, timing advance' }, { cmd: 'AT+NCELL', desc: 'Neighbor cell list with signal levels' }, { cmd: 'AT+RSRP', desc: 'Reference Signal Received Power (LTE/NR)' }, { cmd: 'AT+RSRQ', desc: 'Reference Signal Received Quality (LTE/NR)' }, { cmd: 'AT+RSCP', desc: 'Received Signal Code Power (3G WCDMA)' }, { cmd: 'AT+ECNO', desc: 'Ec/No ratio (3G signal quality metric)' }, { cmd: 'AT$RSRP', desc: 'Extended RSRP — detailed per-antenna/per-carrier readout' }, { cmd: 'AT$RSRQ', desc: 'Extended RSRQ — detailed per-antenna/per-carrier readout' }, { cmd: 'AT$CSQ', desc: 'Extended signal quality beyond standard +CSQ' }, { cmd: 'AT+CESQ', desc: 'Extended signal: RXLEV, BER, RSCP, Ec/No, RSRQ, RSRP, SINR (all in one)' }, { cmd: 'AT+CSQ', desc: 'Basic signal quality — RSSI (0-31) and BER (0-7)' }, { cmd: 'AT+NBLTESCAN', desc: 'NB-IoT / LTE cell scan — finds all visible cells' }, { cmd: 'AT*CNTI', desc: 'Current Network Technology Indicator (GSM/WCDMA/LTE/NR)' }, ], 'Registration & Network': [ { cmd: 'AT+CREG?', desc: 'CS (circuit-switched) registration — 2G/3G voice' }, { cmd: 'AT+CGREG?', desc: 'PS (packet-switched) registration — GPRS/EDGE data' }, { cmd: 'AT+CEREG?', desc: 'EPS registration — LTE status, TAC, cell ID' }, { cmd: 'AT$CREG', desc: 'Extended registration with extra detail' }, { cmd: 'AT+COPS?', desc: 'Current operator (MCC/MNC, access tech)' }, { cmd: 'AT+COPS=?', desc: 'Scan all networks (~30 seconds, returns PLMN list)' }, { cmd: 'AT$ROAM', desc: 'Roaming status and configuration' }, { cmd: 'AT+COPN', desc: 'Read operator name lookup table' }, ], 'Radio Control': [ { cmd: 'AT+CFUN=1', desc: 'Turn radio ON (full functionality)' }, { cmd: 'AT+CFUN=0', desc: 'Turn radio OFF (minimum functionality)' }, { cmd: 'AT+CFUN=4', desc: 'Airplane mode (TX disabled, some RX may work)' }, { cmd: 'AT+CFUN?', desc: 'Query current radio state' }, { cmd: 'AT+WS46?', desc: 'Wireless data service type selection' }, { cmd: 'AT+CEMODE?', desc: 'UE mode of operation — CS only, PS only, CS+PS' }, { cmd: 'AT+CVMOD?', desc: 'Voice mode preference (CS voice vs VoLTE)' }, { cmd: 'AT+CGATT=1', desc: 'PS attach (connect to data)' }, { cmd: 'AT+CGATT=0', desc: 'PS detach (disconnect data)' }, { cmd: 'AT+HANDLEDRX', desc: 'DRX (Discontinuous Reception) control — affects power/latency' }, { cmd: 'AT+CPSMS?', desc: 'Power Saving Mode config (for IoT/battery optimization)' }, { cmd: 'AT+CCIOTOPT?', desc: 'CIoT EPS optimization — control plane vs user plane' }, ], '5G / NR / Network Slicing': [ { cmd: 'AT+C5GNSSAI?', desc: '5G network slice selection (S-NSSAI list)' }, { cmd: 'AT+C5GNSSAIRDP', desc: 'Read dynamic 5G NSSAI parameters' }, { cmd: 'AT+C5GPNSSAI?', desc: '5G preferred NSSAI configuration' }, { cmd: 'AT+C5GQOS?', desc: '5G QoS flow parameters (5QI, MBR, GBR)' }, { cmd: 'AT+C5GUSMS?', desc: '5G USMS (User Services Management) config' }, { cmd: 'AT+CSUPI?', desc: 'SUPI — 5G Subscription Permanent Identifier' }, ], 'Data / APN / Bearer': [ { cmd: 'AT+CGDCONT?', desc: 'PDP context list — all configured APNs' }, { cmd: 'AT+CGPADDR', desc: 'PDP addresses — current IP assignments per CID' }, { cmd: 'AT+CGACT?', desc: 'PDP context activation status' }, { cmd: 'AT+CGCONTRDP', desc: 'Read dynamic PDP context parameters' }, { cmd: 'AT+CGEQOS?', desc: 'EPS bearer QoS parameters' }, { cmd: 'AT+CGEQOSRDP', desc: 'Read dynamic EPS QoS' }, { cmd: 'AT+CGTFT?', desc: 'Traffic Flow Template — packet filters' }, { cmd: 'AT+CGTFTRDP', desc: 'Read dynamic TFT parameters' }, { cmd: 'AT+CGAUTH?', desc: 'PDP context authentication (PAP/CHAP)' }, { cmd: 'AT+CGDSCONT?', desc: 'Secondary PDP context definitions' }, { cmd: 'AT+CGSCONTRDP', desc: 'Read dynamic secondary PDP params' }, { cmd: 'AT+CGEREP?', desc: 'Packet domain event reporting' }, { cmd: 'AT+CRTDCP?', desc: 'Reporting of terminating data via control plane' }, { cmd: 'AT+CSODCP?', desc: 'Sending originating data via control plane' }, { cmd: 'AT+PING', desc: 'Modem-level ping test (bypasses Android IP stack)' }, ], 'IMS (IP Multimedia Subsystem)': [ { cmd: 'AT+AIMSCH', desc: 'IMS channel status' }, { cmd: 'AT+AIMSCH8', desc: 'IMS channel status (extended/8-bit)' }, { cmd: 'AT+AIMSCY', desc: 'IMS cycle/session info' }, { cmd: 'AT+AIMSCY8', desc: 'IMS cycle info (extended)' }, { cmd: 'AT+AIMSDB', desc: 'IMS debug information dump' }, { cmd: 'AT+AIMSDEREG', desc: 'IMS deregistration — force disconnect from IMS' }, { cmd: 'AT+AIMSRX', desc: 'IMS receive status/config' }, { cmd: 'AT+AIMSTR', desc: 'IMS transfer/transaction info' }, ], 'SIM & Identity': [ { cmd: 'AT+CPIN?', desc: 'SIM status — READY, SIM PIN, SIM PUK, not inserted' }, { cmd: 'AT+CGSN', desc: 'IMEI(s) — both slots on dual-SIM' }, { cmd: 'AT+CIMI', desc: 'IMSI — subscriber identity from SIM' }, { cmd: 'AT+CNUM', desc: 'Subscriber phone number from SIM' }, { cmd: 'AT+CPLS?', desc: 'Preferred PLMN list selector' }, { cmd: 'AT+CPOL?', desc: 'Preferred operator list from SIM' }, { cmd: 'AT+CUAD', desc: 'UICC application discovery' }, { cmd: 'AT+CCHO', desc: 'Open logical channel to UICC app' }, { cmd: 'AT+CCHC', desc: 'Close logical channel' }, { cmd: 'AT+CGLA', desc: 'Generic UICC logical channel access (raw APDU)' }, { cmd: 'AT+CRSM', desc: 'Restricted SIM access (read/write SIM files)' }, { cmd: 'AT+CSIM', desc: 'Generic SIM access (raw APDU to SIM)' }, { cmd: 'AT+CLCK', desc: 'Facility lock (PIN enable/disable, network lock query)' }, { cmd: 'AT+CPWD', desc: 'Change password (PIN/PIN2/PUK)' }, ], 'Modem Internals & Debug': [ { cmd: 'AT+CGMI', desc: 'Manufacturer: Samsung Electronics' }, { cmd: 'AT+CGMM', desc: 'Model: S5400BUNUELO (Shannon 5400)' }, { cmd: 'AT+CGMR', desc: 'Firmware: g5400i-251201-260127-B-14784805' }, { cmd: 'AT+GCAP', desc: 'Modem capabilities (+CGSM)' }, { cmd: 'AT$ARME', desc: 'ARM Engine — low-level modem subsystem control' }, { cmd: 'AT$ARMEE', desc: 'ARM Engine extended — deeper subsystem access' }, { cmd: 'AT+DEVFEAT', desc: 'Device feature flags — query supported hardware features' }, { cmd: 'AT+CEER', desc: 'Extended error report — last call/data failure cause code' }, { cmd: 'AT+CMEE=2', desc: 'Enable verbose error messages (CME ERROR text)' }, { cmd: 'AT+CLAC', desc: 'List ALL supported AT commands' }, { cmd: 'AT+PACSP?', desc: 'Preferred ACSP setting' }, ], 'Satellite / NTN': [ { cmd: 'AT+SKYLOTEST', desc: 'Satellite IoT (Skylo/NTN) test mode — non-terrestrial network testing' }, ], 'Carrier-Specific (Hidden)': [ { cmd: 'AT+VZWRSRP', desc: 'Verizon RSRP — carrier-specific signal measurement' }, { cmd: 'AT+VZWRSRQ', desc: 'Verizon RSRQ — carrier-specific signal quality' }, { cmd: 'AT+VZWAPNE?', desc: 'Verizon APN entries — hidden APN config' }, { cmd: 'AT+VZWMRUC', desc: 'Verizon most recently used channel' }, { cmd: 'AT+VZWMRUE', desc: 'Verizon most recently used EARFCN' }, { cmd: 'AT+VZWRPLMNC', desc: 'Verizon roaming PLMN config' }, { cmd: 'AT+SKT', desc: 'SK Telecom specific query/config' }, ], 'SMS & Call': [ { cmd: 'AT+CMGF?', desc: 'SMS message format (0=PDU, 1=text)' }, { cmd: 'AT+CMGL="ALL"', desc: 'List SMS messages' }, { cmd: 'AT+CNMI?', desc: 'New message indication settings' }, { cmd: 'AT+CSCA?', desc: 'SMS service center address' }, { cmd: 'AT+CGSMS?', desc: 'Select service for MO SMS (PS vs CS)' }, { cmd: 'AT+CNMPSD', desc: 'Non-MO PS data indication' }, { cmd: 'AT+CLCC', desc: 'List current calls' }, { cmd: 'AT+CHLD=?', desc: 'Call hold/multiparty capabilities' }, { cmd: 'AT+CLIP?', desc: 'Caller ID presentation status' }, { cmd: 'AT+CLIR?', desc: 'Caller ID restriction status' }, { cmd: 'AT+CCWA?', desc: 'Call waiting status' }, { cmd: 'AT+CCFC=?', desc: 'Call forwarding capabilities' }, { cmd: 'AT+CUSD=?', desc: 'USSD capabilities' }, ], }; async function loadTerminal() { config = await api('/api/status') || config; const soc = config.detected_soc || 'unknown'; // Render quick presets const presets = [...AT_PRESETS.common, ...(AT_PRESETS[soc] || [])]; const container = document.getElementById('at-presets'); document.getElementById('at-presets-title').textContent = `Quick Commands — ${soc.toUpperCase()}`; container.innerHTML = presets.map(p => `
${escHtml(p.cmd)}
${p.desc}
`).join(''); // Render discovered commands reference renderDiscoveredCommands(); renderATHistory(); } function runPreset(cmd) { document.getElementById('at-input').value = cmd; sendATCmd(); } async function sendATCmd() { const input = document.getElementById('at-input'); const cmd = input.value.trim(); if (!cmd) return; atHistory.push({ type: 'cmd', text: cmd, time: new Date().toLocaleTimeString() }); renderATHistory(); input.value = ''; const res = await post('/api/at', { cmd, timeout: '5' }); if (res && res.response) { atHistory.push({ type: 'resp', text: res.response, time: new Date().toLocaleTimeString() }); } else { atHistory.push({ type: 'err', text: 'No response / timeout', time: new Date().toLocaleTimeString() }); } renderATHistory(); } function renderATHistory() { const el = document.getElementById('at-history'); if (atHistory.length === 0) { el.innerHTML = '
No commands sent yet. Type an AT command or tap a preset below.
'; return; } el.innerHTML = atHistory.map(h => { if (h.type === 'cmd') return `
> ${escHtml(h.text)} ${h.time}
`; if (h.type === 'err') return `
${escHtml(h.text)}
`; return `
${escHtml(h.text)}
`; }).join(''); el.scrollTop = el.scrollHeight; } function renderDiscoveredCommands() { const container = document.getElementById('discovered-commands'); if (!container) return; container.innerHTML = Object.entries(DISCOVERED_COMMANDS).map(([category, cmds]) => `
${category}
${cmds.length} commands
tap to expand
${cmds.map(c => `
${escHtml(c.cmd)}
${c.desc}
`).join('')}
`).join(''); } // ---- WiFi page ---- async function loadWifi() { config = await api('/api/status') || config; // Kmod status const kmod = await api('/api/kmod'); if (kmod) { updateKmodBtn('btn-kmod-wifi', kmod.wifi_mon); updateKmodBtn('btn-kmod-shannon', kmod.shannon_cmd); updateKmodBtn('btn-kmod-diag', kmod.diag_bridge); setChip('chip-kmod', kmod.wifi_mon || kmod.shannon_cmd || kmod.diag_bridge); } // WiFi mode buttons document.querySelectorAll('.mode-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.mode === (config.wifi_mode || 'managed')); }); // BCM4390 driver parameters const params = await api('/api/wifi/params'); const pc = document.getElementById('wifi-params'); if (params && params.length > 0) { pc.innerHTML = params.map(p => `
${p.name}
${escHtml(p.value) || '(empty)'}
${p.writable ? `` : ''}
`).join(''); } // WiFi firmware files const fw = await api('/api/wifi/firmware'); const fwc = document.getElementById('wifi-firmware'); if (fw) { let html = ''; if (fw.info) { html += `
Active Firmware
${escHtml(fw.info)}
`; } if (fw.files && fw.files.length > 0) { html += fw.files.map(f => `
${f.name}
${(f.size / 1024).toFixed(0)} KB — ${f.path}
`).join(''); } fwc.innerHTML = html || '
No firmware files found
'; } // WiFi hardware info const wifiText = await fetch('/api/wifi/info').then(r => r.ok ? r.text() : '').catch(() => ''); const wc = document.getElementById('wifi-hw-info'); if (wifiText.trim()) { const lines = wifiText.trim().split('\n').filter(l => l.includes('=')); wc.innerHTML = lines.map(l => { const [k, ...v] = l.split('='); return `
${k}
${v.join('=')}
`; }).join(''); } } async function editWifiParam(name, currentVal) { const newVal = prompt(`Set ${name}:`, currentVal); if (newVal === null) return; const res = await post('/api/wifi/param', { name, value: newVal }); if (res && res.ok) { showToast(`${name} = ${res.value}`); loadWifi(); } else { showToast(res?.error || 'Failed to set param', 'error'); } } function updateKmodBtn(id, loaded) { const btn = document.getElementById(id); if (!btn) return; if (loaded) { btn.textContent = 'Unload'; btn.className = 'btn btn-danger'; btn.style.cssText = 'padding:6px 12px;font-size:12px'; } else { btn.textContent = 'Load'; btn.className = 'btn btn-primary'; btn.style.cssText = 'padding:6px 12px;font-size:12px'; } } async function toggleKmod(mod) { const kmod = await api('/api/kmod'); const key = mod.replace('rc_', '').replace('_cmd', ''); const mapKey = mod === 'rc_wifi_mon' ? 'wifi_mon' : mod === 'rc_shannon_cmd' ? 'shannon_cmd' : 'diag_bridge'; const loaded = kmod && kmod[mapKey]; const action = loaded ? 'unload' : 'load'; const res = await post(`/api/kmod/${action}`, { module: mod }); if (res) { showToast(res.ok ? `${mod}: ${res.status}` : (res.error || 'Failed'), res.ok ? 'success' : 'error'); } loadWifi(); } async function handleWifiMode(mode) { document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); document.querySelector(`[data-mode="${mode}"]`).classList.add('active'); const res = await post('/api/wifi/mode', { mode }); if (res && res.ok) { config.wifi_mode = mode; updateChips(); showToast(`WiFi: ${mode} on ${res.iface}`); } else { showToast(res?.error || 'Failed — load rc_wifi_mon first?', 'error'); loadWifi(); } } // ---- debugfs browser ---- async function loadDebug() { const paths = await api('/api/debugfs'); const sc = document.getElementById('debugfs-shortcuts'); if (paths && paths.length > 0) { sc.innerHTML = paths.map(p => `
${p.replace('/sys/kernel/debug/', '/d/')}
`).join(''); } } function browseToPath(path) { document.getElementById('fs-path').value = path; browsePath(); } async function browsePath() { const path = document.getElementById('fs-path').value.trim(); if (!path) return; const res = await post('/api/fs/read', { path }); const fc = document.getElementById('fs-content'); if (!res) { fc.innerHTML = '
Error reading path
'; return; } // Breadcrumb const parts = path.split('/').filter(Boolean); document.getElementById('fs-breadcrumb').innerHTML = parts.map((p, i) => { const fullPath = '/' + parts.slice(0, i + 1).join('/'); return `${p}`; }).join(' / '); if (res.type === 'dir' && res.entries) { fc.innerHTML = res.entries.map(e => { const icon = e.type === 'dir' ? '📁' : (e.writable ? '📝' : '📄'); return `
${icon} ${e.name}
${e.type}${e.writable ? ' (rw)' : ' (ro)'}
`; }).join('') || '
Empty directory
'; } else if (res.type === 'file') { const content = res.content || '(empty)'; fc.innerHTML = `
${path.split('/').pop()}
${escHtml(content)}
`; } } function readFile(path) { document.getElementById('fs-path').value = path; browsePath(); } function fsUp() { const input = document.getElementById('fs-path'); const parts = input.value.split('/').filter(Boolean); if (parts.length > 1) { parts.pop(); input.value = '/' + parts.join('/'); browsePath(); } } // ---- Carrier page ---- async function loadCarrier() { const cc = await api('/api/carrier/config'); if (cc) { setToggle('toggle-volte', cc.volte); setToggle('toggle-vonr', cc.vonr); setToggle('toggle-wfc', cc.wfc); setToggle('toggle-vt', cc.vt); setToggle('toggle-apn', cc.apn); setToggle('toggle-nradv', cc.nradv); setToggle('toggle-nettype', cc.hide_network_type === '0' ? '1' : '0'); } // Carrier settings files const files = await api('/api/carrier/files'); const fc = document.getElementById('carrier-files'); if (files && files.length > 0) { fc.innerHTML = files.map(f => `
${f.name}
${(f.size / 1024).toFixed(1)} KB
`).join(''); } else { fc.innerHTML = '
No CarrierSettings directory found
'; } } async function handleCarrierFlag(flag, el) { const val = el.checked ? '1' : '0'; const res = await post('/api/carrier/set', { flag, value: val }); if (res && res.ok) { showToast(`${flag} = ${val}`); } else { el.checked = !el.checked; showToast(res?.error || 'Failed', 'error'); } } async function dumpCarrierConfig() { const pre = document.getElementById('carrier-dump'); pre.style.display = 'block'; pre.textContent = 'Loading...'; const res = await api('/api/carrier/dump'); if (res && res.dump) { pre.textContent = res.dump; } else { pre.textContent = 'Failed to dump carrier_config'; } } // ---- CP Debug (in Radio page) ---- async function loadCPDebug() { const cp = await api('/api/cp'); if (cp) { setText('cp-state', cp.state || 'unknown'); document.getElementById('cp-pcie').textContent = cp.pcie_stats ? cp.pcie_stats.substring(0, 200) : 'N/A'; document.getElementById('cp-sbb').textContent = cp.sbb_debug ? cp.sbb_debug.substring(0, 200) : 'N/A'; } } async function triggerCPCrash() { if (!confirm('This will crash the modem and generate a ramdump.\nThe modem will restart automatically.\n\nContinue?')) return; const res = await post('/api/cp/crash', {}); if (res && res.ok) { showToast(res.msg || 'CP crash triggered'); } else { showToast(res?.error || 'Failed', 'error'); } } // ---- Thread / Wonder radio (in Debug page) ---- async function loadThreadInfo() { const info = await api('/api/thread'); const tc = document.getElementById('thread-info'); if (info && info.present) { tc.innerHTML = `
Wonder 802.15.4 Radio
Thread / Matter mesh networking
MAC
${info.mac || 'N/A'}
PHY
${info.name || 'N/A'} (index ${info.index || '?'})
WPAN Interface
${info.wpan_iface ? 'thread-wpan (active)' : 'not present'}
${info.debugfs_entries ? `
debugfs nodes
${info.debugfs_entries}
` : ''} ${info.force_stop_tx !== undefined ? `
force_stop_tx
${info.force_stop_tx}
` : ''} `; } else { tc.innerHTML = '
No Thread/Wonder radio detected
'; } } // ---- Flags page ---- async function loadFlags() { const flags = await api('/api/flags'); const tbody = document.getElementById('flags-body'); if (flags && flags.length > 0) { tbody.innerHTML = flags.map(f => ` ${f.prop} ${f.value || 'unset'} `).join(''); } } // ---- Helpers ---- function escHtml(s) { return (s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } // ---- Init ---- document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.tab-item').forEach(t => t.addEventListener('click', () => switchTab(t.dataset.tab))); // Radio page toggles document.getElementById('toggle-eng')?.addEventListener('change', function() { handleToggle('ENGINEERING_MODE', this); }); document.getElementById('toggle-factory')?.addEventListener('change', function() { handleToggle('FACTORY_TEST_MODE', this); }); document.getElementById('toggle-hidden')?.addEventListener('change', function() { handleToggle('HIDDEN_MENUS', this); }); document.getElementById('toggle-diag')?.addEventListener('change', function() { handleToggle('USB_DIAG_MODE', this); }); document.getElementById('toggle-modem')?.addEventListener('change', function() { handleToggle('MODEM_LOG', this); }); document.getElementById('toggle-stealth')?.addEventListener('change', function() { handleToggle('STEALTH_MODE', this); }); // WiFi mode buttons document.querySelectorAll('.mode-btn').forEach(btn => btn.addEventListener('click', () => handleWifiMode(btn.dataset.mode))); // Carrier config toggles document.getElementById('toggle-volte')?.addEventListener('change', function() { handleCarrierFlag('volte', this); }); document.getElementById('toggle-vonr')?.addEventListener('change', function() { handleCarrierFlag('vonr', this); }); document.getElementById('toggle-wfc')?.addEventListener('change', function() { handleCarrierFlag('wfc', this); }); document.getElementById('toggle-vt')?.addEventListener('change', function() { handleCarrierFlag('vt', this); }); document.getElementById('toggle-apn')?.addEventListener('change', function() { handleCarrierFlag('apn', this); }); document.getElementById('toggle-nradv')?.addEventListener('change', function() { handleCarrierFlag('nradv', this); }); document.getElementById('toggle-nettype')?.addEventListener('change', function() { handleCarrierFlag('nettype', this); }); switchTab('radio'); });