/** * 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, gewichtet nach tatsächlicher spektraler * Überlappung und Signalstärke des Störers. * * Wichtig — hier lag ein Fehler: die erste Fassung rechnete nur mit dem * Abstand der Kanal*nummern* und ignorierte die Kanalbreite komplett. Ein * 40-MHz-Netz auf Kanal 1 belegt real 2402–2442 MHz und damit auch Kanal 6 * (2437), wurde aber wegen `|1−6| = 5` als „stört nicht" gewertet. Seit der * Kanalgraph die Breite korrekt darstellt, widersprachen sich Bild und * Empfehlung sichtbar: der Graph zeigte den breiten Block über Kanal 6, die * Empfehlung nannte Kanal 1 trotzdem als den freisten. Da die Empfehlung das * ist, was am Router eingestellt wird, führte das direkt zur falschen Wahl. * Gerechnet wird deshalb jetzt in MHz — mit derselben `occupiedRange()`, die * auch das Diagramm zeichnet. */ import type { WifiNetwork } from '../scanner'; import { occupiedRange, channelToFreq } from './chart'; const NONOVERLAPPING_24 = [1, 6, 11]; /** Ein WLAN-Kanal im 2,4-GHz-Band ist 20 MHz breit (der Kandidat, den wir belegen wollen) */ const KANDIDAT_BREITE_MHZ = 20; /** * Störlast eines Kandidatenkanals durch ein einzelnes Netz. * * Anteil der Kandidaten-Bandbreite, den das fremde Netz spektral überdeckt * (0…1), gewichtet mit dessen Signalstärke. Ein 40-MHz-Netz überdeckt damit * mehr Kandidatenkanäle als ein 20-MHz-Netz — genau das, was im Kanalgraph * auch zu sehen ist. */ function interferenceWeight(candidate: number, network: WifiNetwork): number { const mitte = channelToFreq(candidate, '2.4 GHz'); const candFrom = mitte - KANDIDAT_BREITE_MHZ / 2; const candTo = mitte + KANDIDAT_BREITE_MHZ / 2; const { from, to } = occupiedRange(network); const ueberlappungMhz = Math.min(candTo, to) - Math.max(candFrom, from); if (ueberlappungMhz <= 0) return 0; const overlapFactor = Math.min(1, ueberlappungMhz / KANDIDAT_BREITE_MHZ); // 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; }