/** * Kanalempfehlung und Warnregeln fürs 2,4-/5-GHz-Band. * * Bewusst einfach gehalten (keine vollständige RF-Simulation): zählt für * jeden der drei nicht überlappenden 2,4-GHz-Kanäle (1/6/11) die Störlast * durch alle sichtbaren Netze — Co-Channel (gleicher Kanal) zählt voll, * Adjacent-Channel (überlappender Bereich, z.B. Kanal 4 bei Kanal 6) zählt * abgeschwächt nach Abstand. Gewichtet zusätzlich mit der Signalstärke des * störenden Netzes (ein schwaches Fremdnetz stört weniger als ein starkes). */ import type { WifiNetwork } from '../scanner'; const NONOVERLAPPING_24 = [1, 6, 11]; /** Kanalabstand zweier 2,4-GHz-Kanäle in "Kanal-Einheiten" (5 MHz je Kanal) */ function channelDistance24(a: number, b: number): number { return Math.abs(a - b); } /** * Störlast eines Kandidatenkanals durch ein einzelnes Netz — 1.0 bei * Co-Channel, linear abnehmend bis 0 bei einem Abstand von 5 Kanälen * (= kein spürbarer Überlapp mehr bei 20-MHz-Kanälen), gewichtet mit der * Signalstärke (stärkeres Fremdnetz stört mehr). */ function interferenceWeight(candidate: number, network: WifiNetwork): number { const dist = channelDistance24(candidate, network.channel); if (dist >= 5) return 0; const overlapFactor = 1 - dist / 5; // RSSI -30 (sehr stark) -> Gewicht ~1, RSSI -90 (kaum sichtbar) -> Gewicht ~0.1 const strengthFactor = Math.min(1, Math.max(0.1, (network.rssi + 90) / 60)); return overlapFactor * strengthFactor; } export interface ChannelScore { channel: number; /** niedriger = besser (weniger Störlast) */ load: number; } /** * Beste der drei nicht überlappenden 2,4-GHz-Kanäle (1/6/11), aufsteigend * nach Störlast sortiert. `networks` sollte NUR Fremdnetze enthalten (das * eigene Netz selbst zählt nicht als Störer für sich selbst). */ export function recommend24GhzChannels(networks: WifiNetwork[]): ChannelScore[] { const in24 = networks.filter((n) => n.band === '2.4 GHz'); return NONOVERLAPPING_24.map((channel) => ({ channel, load: Math.round(in24.reduce((sum, n) => sum + interferenceWeight(channel, n), 0) * 100) / 100, })).sort((a, b) => a.load - b.load); } /** * Zwei feste Warnregeln, die praktisch immer ein Problem anzeigen: * 1. 40 MHz Kanalbreite im 2,4-GHz-Band — bei realistischer Nachbarschafts- * dichte (Mehrfamilienhaus, Gewerbegebiet) fast immer eine Interferenz- * quelle, weil es 2 der 3 nicht überlappenden Kanäle gleichzeitig belegt. * 2. 160 MHz Kanalbreite (oder DFS-Kanäle 52-144) im 5-GHz-Band — DFS kann * durch Radar-Erkennung jederzeit einen Kanalwechsel erzwingen (kurzer * Aussetzer), 160 MHz ist auf vielen Consumer-Geräten instabil bzw. wird * bei Störung automatisch auf 80 MHz zurückgestuft. */ export function channelWarnings(networks: WifiNetwork[]): string[] { const warnings: string[] = []; const wide24 = networks.filter((n) => n.band === '2.4 GHz' && (n.widthMhz ?? 20) >= 40); for (const n of wide24) { warnings.push( `„${n.ssid || n.bssid}" nutzt 40 MHz im 2,4-GHz-Band (Kanal ${n.channel}) — blockiert damit ` + `praktisch 2 der 3 störungsfreien Kanäle. In dicht besiedelter Umgebung meist ein Fehler in ` + `der Router-Konfiguration, nicht Absicht.`, ); } const isDfsChannel = (ch: number) => ch >= 52 && ch <= 144; const wideOrDfs5 = networks.filter( (n) => n.band === '5 GHz' && ((n.widthMhz ?? 20) >= 160 || isDfsChannel(n.channel)), ); for (const n of wideOrDfs5) { const reasons: string[] = []; if ((n.widthMhz ?? 20) >= 160) reasons.push('160 MHz Kanalbreite'); if (isDfsChannel(n.channel)) reasons.push('DFS-Kanal (Radar-Erkennung kann Kanalwechsel erzwingen)'); warnings.push(`„${n.ssid || n.bssid}" (Kanal ${n.channel}): ${reasons.join(', ')} — kann zu kurzen, scheinbar grundlosen Aussetzern führen.`); } return warnings; }