Files
bf-infra/docs/tp25-walk-messung.sh
Christoph Emsenhuber ac3a125c82 TP25/TP28: Walk-Harness auf N Geräte, Analyzer generalisiert, AP-Map-Fix
- tp25-walk-messung.sh: halber Refactor geschlossen — Rumpf schleift ueber die
  PH_*-Arrays (pro Geraet ein Sampler + ein Core-Ping, sampler-<name>.csv /
  walk-ping-<name>.csv), PID-Arrays im Cleanup, Archiv-/Echo-Block generisch.
  Lief vorher unter set -u gar nicht (unbound $PHONE_MAC).
- tp25-gehweg-auswertung.py: liest N benannte Geraete per glob; altes 2-Rollen-
  Schema (sampler.csv/walk-ping.csv/walk-ping-iphone.csv/beide-telefone.csv) bleibt
  als Legacy-Fallback erhalten. Beide Pfade getestet.
- tp25-pin-s22.sh: karte() repariert — channel.frequency ist seit dem DFS-Kanalplan
  (2026-09-03, Frequenz-Listen) ein Array; der alte Skalar-Vergleich blankte map,
  wodurch ap-map.txt leer blieb. Fix nimmt bei :typeof=array das erste Element.

Live-Smoke (4 Geraete) gruen: alle erreichbar, sauberes Cleanup, korrekte AP-Map.
Naechster Schritt: In-situ-Walk (nur Zahlen, Commit/Rollback des Kanalplans separat).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 12:42:30 +02:00

198 lines
9.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# TP25 — Gehweg-Messung: was kostet eine WLAN-Uebergabe wirklich?
#
# Beantwortet die Frage, die die Sampler-Daten offen lassen: bei jedem AP-/Bandwechsel steht in
# der Registration-Table ~4 s lang kein Durchsatz — aber die Zaehler starten nach einer
# Neuanmeldung ohnehin bei null. Ein Ping AUF das Telefon trennt beides:
# Paketverlust = echte Funkstille · kein Verlust = die 4 s waren Buchhaltung.
#
# Gepingt wird VOM CORE aus (Kabel bis zum AP), damit der Verlust eindeutig dem Telefon
# gehoert und nicht der Funkstrecke des messenden Laptops.
#
# Braucht KEINEN Call. Misst nur, aendert nichts.
#
# bash docs/tp25-walk-messung.sh # laeuft bis Ctrl-C bzw. bis "Stoppen" geklickt wird
#
# Waehrend des Gehens auf der Konsole: 🚶 beim Losgehen, 📍 beim Stehenbleiben.
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}"
# 🔴 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-<name>.csv) UND ein eigener Core-Ping (walk-ping-<name>.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)"
mkdir -p "$RUN_DIR"
[[ -f "$MARKERS" ]] || echo "ts_epoch,ts_local,marker" > "$MARKERS"
# Zeiger fuer die Klick-Konsole (docs/tp6-marker/server.py). Die laeuft als Dauer-Preview
# auf Port 3031 und kennt dieses Verzeichnis sonst nicht -- ihre Marker landeten dann im
# Lauf von gestern, waehrend der Sampler hierher schreibt. Beides fiel erst bei der
# Auswertung auf, wenn die Raummarken fehlten.
printf '%s\n' "$RUN_DIR" > "$HERE/docs/tp6-marker/.current-run"
# 🔴 Die AP-Zuordnung JETZT einfrieren, im Messlauf. Die cap-wifiN-Nummern vergibt der
# Manager dynamisch beim Anmelden der CAPs -- sie aendern sich, sobald ein AP neu
# bootet oder umgesteckt wird. Wer sie erst bei der Auswertung nachschlaegt, beschriftet
# alte Daten mit einer neuen Wahrheit. Genau das waere am 2026-08-22 beinahe passiert:
# die Raumkarte vom 08-21 haette Technik OG dann ap1 zugeschrieben -- dem AP, der nicht
# in diesem Raum steht.
bash "$HERE/docs/tp25-pin-s22.sh" map > "$RUN_DIR/ap-map.txt" 2>/dev/null \
&& echo "AP-Zuordnung eingefroren -> $RUN_DIR/ap-map.txt" \
|| echo "⚠️ AP-Zuordnung konnte nicht gelesen werden -- Auswertung bleibt bei cap-wifiN"
# Frueheren Lauf beiseitelegen, statt ihn zu ueberschreiben.
# 🔴 Seit dem N-Geraete-Umbau (2026-09-04) schreiben neue Laeufe pro Geraet
# sampler-<name>.csv / walk-ping-<name>.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 "$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"
fi
state() {
python3 - "$STATE" "$1" <<'PY'
import json, os, sys
p, inst = sys.argv[1], sys.argv[2]
tmp = p + ".tmp"
with open(tmp, "w") as fh:
json.dump({"mode": "langzeit", "phase": "GEHWEG", "title": "Roaming-Messung läuft",
"instruction": inst, "step": 0, "steps": 0, "ends": 0}, fh)
os.replace(tmp, p)
PY
}
state "Kein Call nötig. <b>🚶</b> drücken, wenn du losgehst, <b>📍</b> wenn du stehen bleibst — und bitte den Weg gehen, den du beim Telefonieren wirklich gehst.<br><br>Ich pinge dein Telefon fünfmal pro Sekunde. Jeder AP- oder Bandwechsel zeigt sich dann als echter Paketverlust: <b>so lange ist der Call wirklich tot.</b><br><br>Am Ende: <b>⏹ Aufzeichnung stoppen</b>."
# 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 ->
# Core -> AP -> Telefon). Fehlt dann ein Paket, ist nicht entscheidbar, welches der
# beiden Geraete weg war. Vom Core aus ist die Strecke bis zum AP durchgehend Kabel,
# also misst der Verlust genau das, was er messen soll: den Funk zum Telefon.
# (Aufgefallen am 2026-08-21, als das MacBook waehrend einer Messung vom Dock ans
# WLAN wechselte und dabei auch noch die IP wechselte.)
PW="$(grep -m1 '^CRS326_CORE_PASS=' ~/.secrets/credentials.env | cut -d= -f2- | sed 's/^["'"'"'"'"'"']//; s/["'"'"'"'"'"']$//')"
# $1 = Ziel-IP, $2 = Ausgabedatei. Schreibt "ts_local,seq,ms"; leeres ms = Verlust.
# 🔴 sshpass -e statt -p: mit -p steht das Router-Passwort in der Kommandozeile und damit
# in `ps` und in jedem Prozess-Trace. Am 2026-08-22 ist es genau so im Klartext in einem
# Sitzungsmitschnitt gelandet.
ping_lauf() {
local ziel="$1" datei="$2"
echo "ts_local,seq,ms" > "$datei"
(
while true; do
SSHPASS="$PW" sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=8 \
-o ServerAliveInterval=15 admin@10.40.99.1 \
"/ping $ziel interval=200ms count=100000" 2>/dev/null \
| while IFS= read -r l; do
l="${l%$'\r'}"
case "$l" in
*SEQ*HOST*) continue ;;
esac
seq="$(printf '%s' "$l" | awk '{print $1}')"
case "$seq" in
''|*[!0-9]*) continue ;;
esac
case "$l" in
*timeout*) printf '%s,%s,\n' "$(date +%H:%M:%S)" "$seq" ;;
*) printf '%s,%s,%s\n' "$(date +%H:%M:%S)" "$seq" \
"$(printf '%s' "$l" | awk '{print $NF}')" ;;
esac
done >> "$datei"
printf '%s,,RECONNECT\n' "$(date +%H:%M:%S)" >> "$datei"
sleep 2
done
) &
}
# 🔴 Die PID NICHT ueber $(ping_lauf ...) holen: die Kommandosubstitution wartet, bis die
# 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.
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
# Sampler-Sitzung offen (2026-08-22, beim Test des Stopp-Knopfs gefunden). Wer den Vater
# zuerst killt, verliert den Zugriff auf die Kinder.
killbaum() {
local pid="$1" kind
for kind in $(pgrep -P "$pid" 2>/dev/null); do killbaum "$kind"; done
kill "$pid" 2>/dev/null
}
beenden() {
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.
state "Aufzeichnung gestoppt."
echo; echo "Gestoppt. Auswertung:"
echo " python3 docs/tp25-gehweg-auswertung.py --dir $RUN_DIR"
}
trap beenden EXIT
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
# ausschliesslich per Ctrl-C zu beenden -- obwohl der Anleitungstext auf der Konsole einen
# Stopp-Knopf versprach. Christoph drueckte "Call beendet" und den Not-Aus, beides schrieb
# nur Marken, und die Messung lief weiter. Jetzt gibt es den Knopf wirklich: die Konsole
# legt stop.flag ab, und diese Schleife bricht darauf ab.
rm -f "$RUN_DIR/stop.flag"
while [[ ! -f "$RUN_DIR/stop.flag" ]]; do
touch "$STATE" 2>/dev/null
sleep 2
done
echo "Stopp von der Konsole empfangen."