diff --git a/docs/tp25-gehweg-auswertung.py b/docs/tp25-gehweg-auswertung.py index 35a7df1..112fcb0 100644 --- a/docs/tp25-gehweg-auswertung.py +++ b/docs/tp25-gehweg-auswertung.py @@ -22,6 +22,7 @@ Damit wird aus "irgendwo zwischen 15:07 und 15:09 war es schlecht" ein Satz mit """ import argparse import csv +import glob import os import re import sys @@ -98,24 +99,45 @@ def lade_pings_alt(pfad): return treffer -def geraete(run_dir): - """Liste von Geraeten: (rolle, ping-Liste, funk-Liste). +def _sampler_funk(pfad): + """funk-Liste (sekunde, iface, signal, phy_rate_oder_None) aus einer sampler-CSV.""" + funk = [] + if os.path.exists(pfad): + for r in csv.DictReader(open(pfad)): + if r.get("sta_signal"): + funk.append((sec(r["ts_local"]), r.get("sta_iface") or "", + int(r["sta_signal"]), + int(r["sta_tx_rate"]) if (r.get("sta_tx_rate") or "").isdigit() else None)) + return funk - funk-Liste = (sekunde, iface, signal, phy_rate_oder_None). Die PHY-Rate gibt es nur - fuer die Sonde (sampler.csv, 1-s-Takt); fuer die Referenz liefert beide-telefone.csv - nur AP und Signal im 2-s-Takt. Das ist bewusst so und wird unten beim Ruhezustand - beruecksichtigt -- ohne Rate ist der Befund schwaecher und wird auch so benannt. + +def geraete(run_dir): + """Liste benannter Geraete: (name, ping-Liste, funk-Liste). + + funk-Liste = (sekunde, iface, signal, phy_rate_oder_None). + + Seit 2026-09-04 (N-Geraete-Umbau) schreibt die Harness pro Geraet walk-ping-.csv + UND sampler-.csv -- jedes Paar wird als EIN benanntes Geraet gelesen, beliebig viele, + ohne feste Sonde/Referenz-Rollen. Faellt auf das alte 2-Rollen-Schema zurueck (sampler.csv / + walk-ping.csv / walk-ping-iphone.csv / beide-telefone.csv), damit Laeufe vor dem Umbau + weiter auswertbar bleiben. Unterschieden wird an der NACKTEN sampler.csv/walk-ping.csv: + die schreibt nur das alte Schema -- die neue Harness nie (immer mit -). Ohne diese + Klammer wuerde der glob unten die walk-ping-iphone.csv eines Alt-Laufs fassen und dessen + Sonde (walk-ping.csv, ohne Suffix) stillschweigend verlieren. """ pfad = lambda n: os.path.join(run_dir, n) - sonde_funk = [] - if os.path.exists(pfad("sampler.csv")): - for r in csv.DictReader(open(pfad("sampler.csv"))): - if r.get("sta_signal"): - sonde_funk.append((sec(r["ts_local"]), r.get("sta_iface") or "", - int(r["sta_signal"]), - int(r["sta_tx_rate"]) if (r.get("sta_tx_rate") or "").isdigit() else None)) + legacy = os.path.exists(pfad("sampler.csv")) or os.path.exists(pfad("walk-ping.csv")) + if not legacy: + gs = [] + for pp in sorted(glob.glob(os.path.join(run_dir, "walk-ping-*.csv"))): + name = os.path.basename(pp)[len("walk-ping-"):-len(".csv")] + gs.append((name, lade_pings(pp), _sampler_funk(pfad("sampler-%s.csv" % name)))) + if gs: + return gs + # --- Legacy: 2-Rollen-Schema (Laeufe vor 2026-09-04) --- + sonde_funk = _sampler_funk(pfad("sampler.csv")) ref_funk = [] if os.path.exists(pfad("beide-telefone.csv")): for r in csv.DictReader(open(pfad("beide-telefone.csv"))): @@ -189,6 +211,9 @@ def main(): gs = geraete(a.dir) if not any(g[1] or g[2] for g in gs): raise SystemExit("Weder Ping-Antworten noch Funkproben gefunden.") + # Alte 2-Rollen-Laeufe tragen die Namen "Referenz"/"Sonde" und bekommen den Rollen-Text; + # neue N-Geraete-Laeufe tragen Geraetenamen (iphone/s22/ipad/...) -- ohne Rollen-Erklaerung. + legacy_rollen = any("Sonde" in r or "Referenz" in r for r, _, _ in gs) rooms = [] for m in csv.DictReader(open(os.path.join(a.dir, "markers.csv"))): @@ -209,9 +234,12 @@ def main(): for i, r in enumerate(rooms)] mehrere = len(gs) > 1 - if mehrere: + if mehrere and legacy_rollen: print("REFERENZ = iPhone, roamt frei (was du hier wirklich hast)") print("SONDE = S22, auf den Gegen-AP gezwungen (was die Rückfallebene noch kann)\n") + elif mehrere: + print("Ein Block je Raum, eine Zeile je Gerät (%s) — alle frei roamend.\n" + % ", ".join(r for r, _, _ in gs)) print("%-15s %-17s %5s %-22s %-17s %s" % ("Raum", "Rolle", "Dauer", "Signal min/⌀/max", "Verlust", "längste Lücke")) print("-" * 100) @@ -262,9 +290,13 @@ def main(): print("\nLesehilfe: eine längste Lücke über ~2 s ist für einen Videocall eine hörbare") print("Unterbrechung, über 10 s ist der Call praktisch tot. Signal unter −70 dBm ist die") print("Zone, in der das passiert.") - if mehrere: + if mehrere and legacy_rollen: print("\nDie interessante Spalte ist der Unterschied: gute Referenz + schlechte Sonde") print("= hier hängt alles an EINEM AP. Beide schlecht = echtes Loch ohne Rückfallebene.") + elif mehrere: + print("\nDie interessante Spalte ist der Unterschied ZWISCHEN den Geräten am selben Ort:") + print("streuen Signal/Verlust stark, liegt es am Gerät (Antenne/Standby), nicht am Funk;") + print("sind alle an einem Ort schlecht, ist es ein echtes Loch der Ausleuchtung.") if __name__ == "__main__": diff --git a/docs/tp25-pin-s22.sh b/docs/tp25-pin-s22.sh index 2c350df..7b2c35d 100755 --- a/docs/tp25-pin-s22.sh +++ b/docs/tp25-pin-s22.sh @@ -56,8 +56,14 @@ ros() { } # cap-wifiN -> "apX Band" -- live, ohne Cache. Ausgabe je Zeile: cap-wifi3=ap1/5GHz +# 🔴 channel.frequency ist seit dem DFS-Kanalplan (2026-09-03, Frequenz-Listen 5260,5280 / +# 5300,5320) fuer die 5G-Radios ein ARRAY, kein Skalar. Die alte Zeile verglich das Array +# direkt mit 3000 und lieferte fuer die betroffenen Radios Muell -- den `grep '^cap-wifi'` +# samt pipefail dann verschluckte, sodass `map` LEER zurueckkam und ap-map.txt leer blieb. +# Fix: bei :typeof=array das erste Element nehmen. Beleg-Kommando dieses Fundes am Manager: +# `:foreach ... :put (name . " freq=" . channel.frequency)` -> "5260;5280" fuer cap-wifi1. karte() { - ros ':foreach i in=[/interface wifi find where !master-interface] do={:local c [/interface wifi get $i cap]; :local f [/interface wifi get $i channel.frequency]; :local b "2,4GHz"; :if ($f > 3000) do={:set b "5GHz"}; :put ([/interface wifi get $i name] . "=" . [:pick $c 0 [:find $c "@"]] . "/" . $b)}' \ + ros ':foreach i in=[/interface wifi find where !master-interface] do={:local c [/interface wifi get $i cap]; :local fl [/interface wifi get $i channel.frequency]; :local f 0; :if ([:typeof $fl]="array") do={:set f [:tonum [:pick $fl 0]]} else={:set f [:tonum $fl]}; :local b "2,4GHz"; :if ($f > 3000) do={:set b "5GHz"}; :put ([/interface wifi get $i name] . "=" . [:pick $c 0 [:find $c "@"]] . "/" . $b)}' \ | tr -d '\r' | grep '^cap-wifi[0-9]*=' } diff --git a/docs/tp25-walk-messung.sh b/docs/tp25-walk-messung.sh index e98c193..0bb07f1 100755 --- a/docs/tp25-walk-messung.sh +++ b/docs/tp25-walk-messung.sh @@ -19,14 +19,24 @@ set -uo pipefail RUN_DIR="${TP6_RUN_DIR:-/private/tmp/claude-501/-Users-christophemsenhuber-dev-bf-network-r6/0d86ebd1-035f-4513-a231-5cca8111ecfd/scratchpad/tp6-run2}" -PHONE="${TP25_PHONE_IP:-10.40.10.178}" # S22 -- wird gepingt und gesampelt -PHONE_MAC="${TP25_PHONE_MAC:-2A:0D:B0:5D:AD:A7}" -# 🔴 Seit dem Lauf vom 2026-08-22 wird das ZWEITE Telefon mitgepingt. Vorher lief der Ping -# nur auf das gepinnte Geraet -- und weil das per Automatik immer auf dem GEGEN-AP sitzt, -# fehlte ausgerechnet die Strecke, die die Alltagsabdeckung zeigt. Der Terrassen-Vergleich -# liess sich deshalb nicht fuehren, obwohl beide Telefone dabei waren. -PHONE2="${TP25_PHONE2_IP:-10.40.10.196}" # iPhone -- roamt frei -PHONE2_NAME="${TP25_PHONE2_NAME:-iphone}" + +# 🔴 Handy-Liste generalisiert (2026-09-03, TP25-4-Handy-Messung): statt zwei hartkodierter +# Telefone eine Liste. Format: "name|ip|mac;name|ip|mac;..." -- '|' als Feldtrenner (die MAC +# enthaelt selbst ':'), ';' als Listentrenner. Pro Handy laeuft EIN eigener Sampler +# (sampler-.csv) UND ein eigener Core-Ping (walk-ping-.csv). Default = die +# bisherigen zwei, damit alte Aufrufe unveraendert weiterlaufen. +# Der Core-Ping (5/s) erzeugt zugleich den Dauer-Downlink, der die Taschen-Handys wach haelt. +TP25_PHONES="${TP25_PHONES:-iphone|10.40.10.196|F2:62:4C:E1:AF:09;s22|10.40.10.178|2A:0D:B0:5D:AD:A7}" +declare -a PH_NAME PH_IP PH_MAC +IFS=';' read -ra _ENTRIES <<< "$TP25_PHONES" +for _e in "${_ENTRIES[@]}"; do + [[ -z "${_e//[[:space:]]/}" ]] && continue + IFS='|' read -r _n _ip _mac <<< "$_e" + [[ -n "$_n" && -n "$_ip" && -n "$_mac" ]] || { echo "TP25_PHONES-Eintrag unvollstaendig: '$_e' (erwartet name|ip|mac)" >&2; exit 2; } + PH_NAME+=("$_n"); PH_IP+=("$_ip"); PH_MAC+=("$_mac") +done +[[ ${#PH_NAME[@]} -gt 0 ]] || { echo "TP25_PHONES leer/ungueltig: '$TP25_PHONES'" >&2; exit 2; } + STATE="$RUN_DIR/phase.json" MARKERS="$RUN_DIR/markers.csv" HERE="$(cd "$(dirname "$0")/.." && pwd)" @@ -51,10 +61,17 @@ bash "$HERE/docs/tp25-pin-s22.sh" map > "$RUN_DIR/ap-map.txt" 2>/dev/null \ || echo "⚠️ AP-Zuordnung konnte nicht gelesen werden -- Auswertung bleibt bei cap-wifiN" # Frueheren Lauf beiseitelegen, statt ihn zu ueberschreiben. -if [[ -f "$RUN_DIR/sampler.csv" ]]; then +# 🔴 Seit dem N-Geraete-Umbau (2026-09-04) schreiben neue Laeufe pro Geraet +# sampler-.csv / walk-ping-.csv -- der alte Trigger auf die nackte sampler.csv +# haette einen Re-Run nicht mehr erkannt und den Vorlauf ueberschrieben. Legacy-Namen +# (sampler.csv/walk-ping.csv/beide-telefone.csv aus 2-Rollen-Laeufen) bleiben in der Liste. +if compgen -G "$RUN_DIR/sampler-*.csv" >/dev/null || compgen -G "$RUN_DIR/walk-ping-*.csv" >/dev/null \ + || [[ -f "$RUN_DIR/sampler.csv" ]]; then ARCHIV="$RUN_DIR/archiv-$(date +%H%M%S)"; mkdir -p "$ARCHIV" - for f in sampler.csv markers.csv walk-ping.csv walk-ping.log ping-dauerlauf.log; do - [[ -f "$RUN_DIR/$f" ]] && mv "$RUN_DIR/$f" "$ARCHIV/" + for f in "$RUN_DIR"/sampler.csv "$RUN_DIR"/sampler-*.csv "$RUN_DIR"/sampler-*.out \ + "$RUN_DIR"/markers.csv "$RUN_DIR"/walk-ping.csv "$RUN_DIR"/walk-ping-*.csv \ + "$RUN_DIR"/walk-ping.log "$RUN_DIR"/ping-dauerlauf.log "$RUN_DIR"/beide-telefone.csv; do + [[ -f "$f" ]] && mv "$f" "$ARCHIV/" done echo "ts_epoch,ts_local,marker" > "$MARKERS" echo "Vorheriger Lauf -> $ARCHIV" @@ -74,9 +91,16 @@ PY state "Kein Call nötig. 🚶 drücken, wenn du losgehst, 📍 wenn du stehen bleibst — und bitte den Weg gehen, den du beim Telefonieren wirklich gehst.

Ich pinge dein Telefon fünfmal pro Sekunde. Jeder AP- oder Bandwechsel zeigt sich dann als echter Paketverlust: so lange ist der Call wirklich tot.

Am Ende: ⏹ Aufzeichnung stoppen." -# Sampler am Manager (1-s-Takt, feiner als bei der TP6-Messung) -bash "$HERE/docs/tp6-monitor-core.sh" --out "$RUN_DIR/sampler.csv" --mac "$PHONE_MAC" --interval 1 \ - > "$RUN_DIR/sampler.out" 2>&1 & SAMPLER=$! +# Sampler am Manager -- pro Geraet einer. 2-s-Takt: bei 4 Geraeten laufen sonst 8 SSH-Sessions +# gegen den Core-SPOF, und Signal alle 2 s reicht fuer einen Gehweg (der Ping bleibt fein bei +# 200 ms). Jeder Sampler holt sich den Core-PW selbst (docs/tp6-monitor-core.sh, SSHPASS -e). +declare -a SAMPLERS +for i in "${!PH_NAME[@]}"; do + bash "$HERE/docs/tp6-monitor-core.sh" --out "$RUN_DIR/sampler-${PH_NAME[$i]}.csv" \ + --mac "${PH_MAC[$i]}" --interval 2 \ + > "$RUN_DIR/sampler-${PH_NAME[$i]}.out" 2>&1 & + SAMPLERS+=("$!") +done # 5 Pings/s auf JEDES Telefon — GEPINGT WIRD VOM CORE, nicht von diesem Rechner. # 🔴 Der Grund: pingt der Laptop, laeuft der Weg ueber ZWEI Funkstrecken (Laptop -> AP -> @@ -124,8 +148,11 @@ ping_lauf() { # stdout-Pipe schliesst, und der Hintergrundjob darin haelt sie offen -- das Skript blieb # dort stehen, der zweite Ping wurde nie gestartet (2026-08-22). Die Funktion laeuft in # der aktuellen Shell, also liefert $! danach die richtige PID. -ping_lauf "$PHONE" "$RUN_DIR/walk-ping.csv"; PINGER=$! -ping_lauf "$PHONE2" "$RUN_DIR/walk-ping-$PHONE2_NAME.csv"; PINGER2=$! +declare -a PINGERS +for i in "${!PH_NAME[@]}"; do + ping_lauf "${PH_IP[$i]}" "$RUN_DIR/walk-ping-${PH_NAME[$i]}.csv" + PINGERS+=("$!") +done # 🔴 Kinder ZUERST. `kill $SAMPLER` traf nur den Wrapper; dessen Subshell samt SSH-Sitzung # wurde dabei verwaist und lief weiter -- nach dem Stopp hielt der Core stumm eine @@ -138,9 +165,8 @@ killbaum() { } beenden() { - for pid in $SAMPLER $PINGER $PINGER2; do killbaum "$pid"; done - pkill -f "ping $PHONE interval=200ms" 2>/dev/null - pkill -f "ping $PHONE2 interval=200ms" 2>/dev/null + for pid in "${SAMPLERS[@]}" "${PINGERS[@]}"; do killbaum "$pid"; done + for ip in "${PH_IP[@]}"; do pkill -f "ping $ip interval=200ms" 2>/dev/null; done # Der /ping am Router endet mit der SSH-Sitzung (am 2026-08-22 nachgeprueft: nach dem # Kill der Sitzungen lief am Core nichts weiter). Bewusst KEIN "job remove" am Geraet -- # ein pauschales Aufraeumen fremder Jobs waere ein zu grobes Werkzeug fuer ein Messskript. @@ -150,10 +176,12 @@ beenden() { } trap beenden EXIT -echo "Gehweg-Messung laeuft." -echo " Funk -> $RUN_DIR/sampler.csv" -echo " Ping -> $RUN_DIR/walk-ping.csv ($PHONE, gepinnt)" -echo " Ping -> $RUN_DIR/walk-ping-$PHONE2_NAME.csv ($PHONE2, frei roamend)" +echo "Gehweg-Messung laeuft (${#PH_NAME[@]} Geraete, alle frei roamend)." +for i in "${!PH_NAME[@]}"; do + printf ' %-9s Funk -> sampler-%s.csv Ping -> walk-ping-%s.csv (%s)\n' \ + "${PH_NAME[$i]}" "${PH_NAME[$i]}" "${PH_NAME[$i]}" "${PH_IP[$i]}" +done +echo " Verzeichnis: $RUN_DIR" echo " Stoppen: '⏹ Aufzeichnung stoppen' auf der Konsole -- oder Ctrl-C hier." # 🔴 Bis 2026-08-22 lief hier nur `while true; do touch; sleep 5; done`, und das Skript war