Compare commits

..

2 commits

Author SHA1 Message Date
2d2fa3836d Trigger: APK-Rebuild [apk]
All checks were successful
Build APK / build-apk (push) Successful in 2m6s
2026-08-15 17:46:04 +02:00
2d35c6b729 WLAN-Demodaten: Kanalanalyse ohne echten Empfang ansehen
Auf Eddys Vorschlag. Der Android-Emulator hat keine WLAN-Hardware und liefert
bei jedem Scan null Netze — Kanalgraph, Gruppierung, Kanalempfehlung und die
Warnregeln ließen sich damit auf dem Gerät überhaupt nicht ansehen. Genau
deshalb ging die erste Fassung der Diagramme unbemerkt unlesbar in den Einsatz.

Schalter in den Einstellungen (Diagnose → WLAN-Demodaten). Ist er an, zeigt die
Kanalanalyse eine realistische Beispielumgebung statt echter Messwerte. Die
Daten decken bewusst die Grenzfälle ab: Mesh über zwei Bänder, mehrere Netze
auf demselben Kanal, ein 40-MHz-Netz im 2,4-GHz-Band und ein DFS- sowie ein
160-MHz-Netz im 5-GHz-Band (lösen beide Warnregeln aus), Netze in allen drei
UNII-Blöcken, ein Netz am Bandrand, ein offenes Netz ohne Standard-Angabe.
Leichtes Rauschen je Scan, damit mehrere Momentaufnahmen einen Zeitverlauf mit
sichtbarem Auf und Ab ergeben.

Damit keine Demo je für eine echte Messung gehalten wird:
- deutlich sichtbarer Hinweis im Kopf der Seite, solange der Modus an ist
- Messungs-Label beginnt mit "DEMODATEN — "
- result-JSON trägt demodaten:true; Dolibarr zeigt das in der Kartenansicht und
  im PDF als eigene Zeile (PDF besonders wichtig, das Dokument geht zum Kunden)

Im Emulator durchgespielt: Schalter an -> Kanalanalyse zeigt alle Diagramme mit
Netzen, 5-GHz-Band in drei lesbaren UNII-Blöcken, Fazit-Zeile und alle drei
Warnhinweise korrekt. Momentaufnahme gespeichert, synchronisiert und in der
Test-DB geprüft: Label und result-Flag korrekt als Demo markiert.

Zweitnutzen: Vorführung beim Kunden, unabhängig davon was gerade in der Luft ist.
2026-08-15 17:45:52 +02:00
3 changed files with 194 additions and 0 deletions

131
src/lib/wifi/demo.ts Normal file
View file

@ -0,0 +1,131 @@
/**
* WLAN-Demodaten für Vorführung und Prüfung ohne echten Empfang.
*
* Warum das nötig ist: der Android-Emulator hat keine WLAN-Hardware und
* liefert bei jedem Scan null Netze. Kanalgraph, Gruppierung, Kanalempfehlung
* und die Warnregeln ließen sich damit auf dem Gerät überhaupt nicht ansehen
* die erste Fassung der Diagramme ging deshalb unbemerkt unlesbar in den
* Einsatz. Mit diesem Schalter zeigt die App eine realistische Kundensituation
* und die gesamte Anzeigekette lässt sich vor Ort wie im Emulator prüfen.
*
* Zweiter Nutzen: Vorführung beim Kunden ohne Abhängigkeit davon, was gerade
* in der Luft liegt.
*
* Die Daten sind bewusst so gewählt, dass sie die Grenzfälle abdecken:
* - Mesh (dieselbe SSID auf mehreren BSSIDs und Bändern)
* - mehrere Netze auf demselben Kanal (Co-Channel)
* - ein 40-MHz-Netz im 2,4-GHz-Band (löst Warnregel 1 aus)
* - ein DFS-Kanal und ein 160-MHz-Netz im 5-GHz-Band (löst Warnregel 2 aus)
* - Netze in allen drei UNII-Blöcken, damit die 5-GHz-Aufteilung sichtbar wird
* - ein Netz am Bandrand und ein offenes Netz ohne Standard-Angabe
*/
import { Preferences } from '@capacitor/preferences';
import type { WifiNetwork } from '../scanner';
const KEY = 'netdiag_wifi_demo';
/** BSSID des Netzes, das im Demomodus als „verbunden" gilt */
export const DEMO_CONNECTED_BSSID = 'AA:BB:CC:10:20:01';
export const DEMO_NETWORKS: WifiNetwork[] = [
// Eigenes Netz (Mesh über 2,4 + 5 GHz)
{
ssid: 'FRITZ!Box 7590 SM', bssid: DEMO_CONNECTED_BSSID, channel: 6, frequency: 2437,
rssi: -48, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 800,
},
{
ssid: 'FRITZ!Box 7590 SM', bssid: 'AA:BB:CC:10:20:02', channel: 44, frequency: 5220,
rssi: -55, band: '5 GHz', widthMhz: 80, centerFreq0: 5210,
standard: 'Wi-Fi 6 (802.11ax)', security: 'WPA3', ageMs: 900,
},
// Gastnetz auf demselben Kanal wie das Hauptnetz (Co-Channel)
{
ssid: 'Sandmann-Gast', bssid: 'AA:BB:CC:10:20:03', channel: 6, frequency: 2437,
rssi: -52, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 1100,
},
// Repeater (Mesh, zweiter Standort) — 5 GHz auf DFS-Kanal (Warnregel)
{
ssid: 'Repeater-Halle', bssid: 'AA:BB:CC:10:20:04', channel: 11, frequency: 2462,
rssi: -61, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 4 (802.11n)', security: 'WPA2', ageMs: 2200,
},
{
ssid: 'Repeater-Halle', bssid: 'AA:BB:CC:10:20:05', channel: 100, frequency: 5500,
rssi: -70, band: '5 GHz', widthMhz: 80, centerFreq0: 5530,
standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 2600,
},
// Nachbar mit 40 MHz im 2,4-GHz-Band (Warnregel)
{
ssid: 'Nachbar-WLAN-1', bssid: 'DD:EE:FF:30:40:01', channel: 1, frequency: 2412,
rssi: -74, band: '2.4 GHz', widthMhz: 40, centerFreq0: 2422,
standard: 'Wi-Fi 4 (802.11n)', security: 'WPA2', ageMs: 3400,
},
{
ssid: 'Telekom-2AB3F', bssid: 'DD:EE:FF:30:40:02', channel: 11, frequency: 2462,
rssi: -79, band: '2.4 GHz', widthMhz: 20, standard: 'Wi-Fi 4 (802.11n)', security: 'WPA2', ageMs: 4100,
},
// Offenes Netz am oberen Bandrand, ohne Standard-Angabe
{
ssid: 'Vodafone-Hotspot', bssid: 'DD:EE:FF:30:40:03', channel: 13, frequency: 2472,
rssi: -85, band: '2.4 GHz', widthMhz: 20, security: 'offen', ageMs: 5200,
},
// UNII-1: schwacher Nachbar + Drucker-Direktverbindung
{
ssid: 'o2-WLAN-9912', bssid: 'DD:EE:FF:30:40:04', channel: 36, frequency: 5180,
rssi: -76, band: '5 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 3900,
},
{
ssid: 'DIRECT-hp-OfficeJet', bssid: 'DD:EE:FF:30:40:05', channel: 48, frequency: 5240,
rssi: -81, band: '5 GHz', widthMhz: 20, standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 4400,
},
// UNII-3: Kamera + zweiter Büro-AP mit 160 MHz (Warnregel)
{
ssid: 'Kamera-Hof-5G', bssid: 'DD:EE:FF:30:40:06', channel: 149, frequency: 5745,
rssi: -72, band: '5 GHz', widthMhz: 40, centerFreq0: 5755,
standard: 'Wi-Fi 5 (802.11ac)', security: 'WPA2', ageMs: 2900,
},
{
ssid: 'Buero-AP-2', bssid: 'DD:EE:FF:30:40:07', channel: 157, frequency: 5785,
rssi: -68, band: '5 GHz', widthMhz: 160, centerFreq0: 5775,
standard: 'Wi-Fi 6 (802.11ax)', security: 'WPA2', ageMs: 1800,
},
];
/**
* Demodaten mit leichtem Rauschen so verändert sich das Bild zwischen zwei
* Scans wie in der Wirklichkeit, und mehrere Momentaufnahmen ergeben einen
* Zeitverlauf mit sichtbarem Auf und Ab statt völlig gerader Linien.
*/
export function demoNetworks(): WifiNetwork[] {
return DEMO_NETWORKS.map((n) => ({
...n,
rssi: Math.max(-92, Math.min(-30, n.rssi + Math.round((Math.random() - 0.5) * 6))),
}));
}
/* --- Schalter (überlebt den App-Neustart) --- */
let aktiv = false;
export function isDemoAktiv(): boolean {
return aktiv;
}
export async function ladeDemoSchalter(): Promise<boolean> {
try {
const { value } = await Preferences.get({ key: KEY });
aktiv = value === '1';
} catch {
aktiv = false;
}
return aktiv;
}
export async function setzeDemoSchalter(an: boolean): Promise<void> {
aktiv = an;
try {
await Preferences.set({ key: KEY, value: an ? '1' : '0' });
} catch {
/* Schalter gilt dann nur für diese Sitzung */
}
}

View file

@ -7,11 +7,28 @@
import { sync } from '$lib/sync.svelte'; import { sync } from '$lib/sync.svelte';
import { toast } from '$lib/toast.svelte'; import { toast } from '$lib/toast.svelte';
import { APP_VERSION, checkForUpdate, installUpdate, type UpdateInfo } from '$lib/updater'; import { APP_VERSION, checkForUpdate, installUpdate, type UpdateInfo } from '$lib/updater';
import { ladeDemoSchalter, setzeDemoSchalter } from '$lib/wifi/demo';
import { onMount } from 'svelte';
let checking = $state(false); let checking = $state(false);
let available = $state<UpdateInfo | null>(null); let available = $state<UpdateInfo | null>(null);
let installing = $state(false); let installing = $state(false);
let percent = $state(0); let percent = $state(0);
let wifiDemo = $state(false);
onMount(async () => {
wifiDemo = await ladeDemoSchalter();
});
async function toggleWifiDemo(e: Event) {
const an = (e.currentTarget as HTMLInputElement).checked;
wifiDemo = an;
await setzeDemoSchalter(an);
toast.show(
an ? 'WLAN-Demodaten an — Kanalanalyse zeigt Beispieldaten' : 'WLAN-Demodaten aus',
an ? 'info' : 'success',
);
}
// Sync-Status in Klartext // Sync-Status in Klartext
const syncLabel = $derived( const syncLabel = $derived(
@ -220,6 +237,26 @@
> >
Debug-Log öffnen Debug-Log öffnen
</a> </a>
<!-- WLAN-Demodaten: der Emulator (und ein Gerät ohne Empfang) liefert
keine Netze — damit ließe sich die Kanalanalyse gar nicht ansehen.
Auch für die Vorführung beim Kunden brauchbar. -->
<label class="mt-3 flex items-start gap-2.5">
<input
type="checkbox"
class="mt-0.5 h-4 w-4 shrink-0 accent-fuchsia-500"
checked={wifiDemo}
onchange={toggleWifiDemo}
/>
<span class="text-sm">
WLAN-Demodaten
<span class="mt-0.5 block text-xs text-zinc-500">
Die WLAN-Kanalanalyse zeigt dann eine erfundene Beispielumgebung statt echter
Messwerte — zum Ansehen und Vorführen ohne Empfang. Gespeicherte Momentaufnahmen
werden deutlich als Demo gekennzeichnet.
</span>
</span>
</label>
</section> </section>
<button <button

View file

@ -21,6 +21,7 @@
import { rssiRating } from '$lib/wifi/rating'; import { rssiRating } from '$lib/wifi/rating';
import { recommend24GhzChannels, channelWarnings } from '$lib/wifi/recommend'; import { recommend24GhzChannels, channelWarnings } from '$lib/wifi/recommend';
import { netColor } from '$lib/wifi/chart'; import { netColor } from '$lib/wifi/chart';
import { demoNetworks, ladeDemoSchalter, DEMO_CONNECTED_BSSID } from '$lib/wifi/demo';
import { sync } from '$lib/sync.svelte'; import { sync } from '$lib/sync.svelte';
import { toast } from '$lib/toast.svelte'; import { toast } from '$lib/toast.svelte';
import type { MeasureStatus, Protocol, WifiSurvey } from '$lib/types'; import type { MeasureStatus, Protocol, WifiSurvey } from '$lib/types';
@ -32,6 +33,7 @@
let locationError = $state(false); let locationError = $state(false);
let errorMsg = $state(''); let errorMsg = $state('');
let expandedSurvey = $state<string | null>(null); let expandedSurvey = $state<string | null>(null);
let demoAktiv = $state(false);
const BANDS = ['2.4 GHz', '5 GHz', '6 GHz'] as const; const BANDS = ['2.4 GHz', '5 GHz', '6 GHz'] as const;
@ -88,6 +90,7 @@
return; return;
} }
protocol = p; protocol = p;
demoAktiv = await ladeDemoSchalter();
await refresh(); await refresh();
}); });
@ -104,6 +107,16 @@
locationError = false; locationError = false;
errorMsg = ''; errorMsg = '';
try { try {
// Demomodus: der Emulator hat keine WLAN-Hardware und liefert immer 0
// Netze — damit ließe sich die gesamte Anzeige nicht prüfen. Der
// Schalter steht in den Einstellungen und ist im Kopf der Seite deutlich
// sichtbar, damit keine Demomessung versehentlich als echte Messung im
// Kundenprotokoll landet.
if (demoAktiv) {
networks = demoNetworks();
connectedBssid = DEMO_CONNECTED_BSSID;
return;
}
const r = await scanner.wifiScan({ trigger: true }); const r = await scanner.wifiScan({ trigger: true });
networks = r.networks; networks = r.networks;
try { try {
@ -168,11 +181,16 @@
tool: 'wifikanal', tool: 'wifikanal',
category: 'netzwerk', category: 'netzwerk',
label: label:
(demoAktiv ? 'DEMODATEN — ' : '') +
`${survey.networks.length} WLAN-Netze` + `${survey.networks.length} WLAN-Netze` +
(eigenStk ? ` · eigenes Netz Kanal ${eigenStk.channel}, ${eigenStk.rssi} dBm` : '') + (eigenStk ? ` · eigenes Netz Kanal ${eigenStk.channel}, ${eigenStk.rssi} dBm` : '') +
(recommendation.length > 0 ? ` · 2,4 GHz frei: Kanal ${recommendation[0].channel}` : ''), (recommendation.length > 0 ? ` · 2,4 GHz frei: Kanal ${recommendation[0].channel}` : ''),
params: {}, params: {},
result: { result: {
// Muss im Protokoll sichtbar bleiben: eine Demomessung darf nie mit
// einer echten Messung verwechselt werden — auch nicht Wochen später
// im Kunden-PDF.
...(demoAktiv ? { demodaten: true } : {}),
anzahlNetze: survey.networks.length, anzahlNetze: survey.networks.length,
eigenesNetz: eigenStk eigenesNetz: eigenStk
? { ? {
@ -278,6 +296,14 @@
</button> </button>
</div> </div>
{#if demoAktiv}
<p class="mt-2 rounded-lg bg-fuchsia-900/40 p-2.5 text-xs text-fuchsia-200">
<strong>Demodaten aktiv</strong> — die angezeigten Netze sind erfunden, nicht gemessen.
Eine hier gespeicherte Momentaufnahme wird als Demo gekennzeichnet. Abschalten unter
Einstellungen → WLAN-Demodaten.
</p>
{/if}
{#if fazit} {#if fazit}
<p class="mt-2 rounded-lg bg-zinc-800 p-2.5 text-sm font-medium text-zinc-100">{fazit}</p> <p class="mt-2 rounded-lg bg-zinc-800 p-2.5 text-sm font-medium text-zinc-100">{fazit}</p>
{/if} {/if}