2 Commits

Author SHA1 Message Date
efb0f44fa7 TP25: Multi-Pin-Tool tp25-pin-multi.sh (band-genaues Pinnen, N Clients je 1 AP)
Fuer die gepinnte Pro-AP-Abdeckungsmessung: pinnt mehrere Clients gleichzeitig
band-genau auf je einen AP (je Sonde alle cap-wifiN AUSSER dem Ziel per
access-list reject, eigener Tag TP25-PINM-<name>), iPhone bleibt frei. Loest
AP+Band -> cap-wifiN live aus der Karte (reuse tp25-pin-s22.sh map), cacht nie.
Modi set/verify/off/map. 3.2-safe (keine assoziativen Arrays, macOS /bin/bash).
access-list steht unter keiner ComplianceRule -> kein Nautobot-Drift; off raeumt
vollstaendig auf.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 13:56:55 +02:00
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
4 changed files with 210 additions and 39 deletions

View File

@@ -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-<name>.csv
UND sampler-<name>.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 -<name>). 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__":

105
docs/tp25-pin-multi.sh Executable file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env bash
#
# TP25 — MEHRERE Clients gleichzeitig band-genau auf je EINEN AP pinnen.
#
# Wozu (2026-09-04): die Gehweg-Messung soll pro AP getrennt zeigen, wie weit er reicht. Dazu
# wird je Sonde GENAU EIN cap-wifiN erlaubt (also ein AP UND ein Band) und alle anderen per
# access-list abgewiesen. Das iPhone bleibt bewusst UNGEPINNT (frei roamende Referenz).
#
# Unterschied zu tp25-pin-s22.sh: das pinnt EIN Geraet AP-genau (beide Baender). Hier: N
# Geraete, band-genau, je eigener Tag TP25-PINM-<name> -- damit `off` sauber nur diese entfernt.
#
# set: TP25_PINSPEC="ipad|CA:..|ap1|5;macbook|56:..|ap2|5;s22|2A:..|ap3|5" bash tp25-pin-multi.sh set
# verify: bash tp25-pin-multi.sh verify # zeigt je Sonde das aktuelle Interface
# off: bash tp25-pin-multi.sh off # alle TP25-PINM-* weg
#
# Band im Spec: "5" = 5 GHz, "24"/"2"/"2.4" = 2,4 GHz. AP: ap1|ap2|ap3 (wie in der Live-Karte).
#
# 🔴 Die cap-wifiN-Zuordnung wird bei JEDEM Aufruf frisch aus der Karte (tp25-pin-s22.sh map)
# gelesen -- sie wandert bei jedem CAP-Rejoin. Niemals cachen.
# 🔴 access-list steht unter KEINER ComplianceRule -> kein Nautobot-Drift. Jede Regel traegt
# ihren Tag; `off` raeumt vollstaendig auf.
# 🔴 Bewusst KEINE assoziativen Arrays (declare -A): macOS /bin/bash ist 3.2. Lookup per grep.
set -uo pipefail
TAG_PREFIX="TP25-PINM"
ROUTER_IP="${TP25_MANAGER_IP:-10.40.99.1}"
HERE="$(cd "$(dirname "$0")" && pwd)"
ZIEL="${1:-verify}"
PW="$(grep -m1 '^CRS326_CORE_PASS=' ~/.secrets/credentials.env | cut -d= -f2- | sed 's/^["'"'"']//; s/["'"'"']$//')"
[[ -n "$PW" ]] || { echo "CRS326_CORE_PASS nicht in ~/.secrets/credentials.env gefunden" >&2; exit 1; }
# 🔴 sshpass -e statt -p: PW nie in die Kommandozeile (ps/Trace-Leak, 2026-08-22). Ausgabe zusaetzlich maskiert.
ros() {
SSHPASS="$PW" sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=8 \
"admin@${ROUTER_IP}" "$1" 2>&1 | sed "s/${PW//\//\\/}/***/g"
}
# Live-Karte (cap-wifiN=apX/Band, je Zeile) als String. Reuse von tp25-pin-s22.sh (map).
MAP=""
declare -a ALL_IFACES
lade_karte() {
MAP="$(bash "$HERE/tp25-pin-s22.sh" map)"
[[ -n "${MAP//[[:space:]]/}" ]] || { echo "Live-Karte leer -- haengen die CAPs am Manager?" >&2; exit 1; }
ALL_IFACES=()
while IFS='=' read -r iface _label; do [[ -n "$iface" ]] && ALL_IFACES+=("$iface"); done <<< "$MAP"
}
label_of() { grep -m1 "^$1=" <<< "$MAP" | cut -d= -f2-; }
# ap + band -> cap-wifiN (aus der Karte). Band-Eingabe normalisieren.
ziel_iface() { # $1=ap $2=band
local want
case "$2" in
5|5g|5G|5GHz) want="$1/5GHz" ;;
24|2|2.4|2,4|2g|2G) want="$1/2,4GHz" ;;
*) echo "" ; return ;;
esac
grep -m1 "=${want}\$" <<< "$MAP" | cut -d= -f1
}
kick() { ros "/interface wifi registration-table remove [find where mac-address=\"$1\"]" >/dev/null; }
aufraeumen_alle() { ros "/interface wifi access-list remove [find where comment~\"$TAG_PREFIX\"]" >/dev/null; }
case "$ZIEL" in
set)
[[ -n "${TP25_PINSPEC:-}" ]] || { echo "TP25_PINSPEC fehlt (name|mac|ap|band;...)" >&2; exit 2; }
lade_karte
aufraeumen_alle
IFS=';' read -ra ENTRIES <<< "$TP25_PINSPEC"
for e in "${ENTRIES[@]}"; do
[[ -z "${e//[[:space:]]/}" ]] && continue
IFS='|' read -r name mac ap band <<< "$e"
[[ -n "$name" && -n "$mac" && -n "$ap" && -n "$band" ]] || { echo "Spec-Eintrag unvollstaendig: '$e'" >&2; exit 2; }
ziel="$(ziel_iface "$ap" "$band")"
[[ -n "$ziel" ]] || { echo "Kein Interface fuer $ap/$band in der Karte -- AP am Manager? Abbruch." >&2; exit 1; }
tag="${TAG_PREFIX}-${name}"
# Alle Interfaces AUSSER dem Ziel abweisen -> nur $ziel bleibt erlaubt (AP + Band erzwungen).
gesperrt=""
for i in "${ALL_IFACES[@]}"; do
[[ "$i" == "$ziel" ]] && continue
ros "/interface wifi access-list add mac-address=$mac interface=$i action=reject comment=\"$tag\"" >/dev/null
gesperrt="$gesperrt $i"
done
echo "$name ($mac) -> $ziel ($(label_of "$ziel")); abgewiesen:$gesperrt"
kick "$mac"
done
echo; echo "Gesetzt. Kontrolle in ~6 s: bash $0 verify" ;;
off)
aufraeumen_alle; echo "Alle ${TAG_PREFIX}-* entfernt." ;;
verify)
lade_karte
echo "--- aktive ${TAG_PREFIX}-Regeln ---"
r="$(ros "/interface wifi access-list print terse where comment~\"$TAG_PREFIX\"")"
[[ -z "${r//[[:space:]]/}" ]] && echo " (keine)" || echo "$r" | tr -d '\r' | sed 's/^/ /'
echo "--- aktuelle Anmeldungen (Ziel-Kontrolle) ---"
a="$(ros '/interface wifi registration-table print terse proplist=mac-address,interface,signal,ssid')"
echo "$a" | tr -d '\r' | grep -E 'mac-address=' | while IFS= read -r line; do
ifc="$(sed -n 's/.*interface=\([^ ]*\).*/\1/p' <<< "$line")"
echo " ${line} [$(label_of "$ifc")]"
done ;;
map) bash "$HERE/tp25-pin-s22.sh" map ;;
*) echo "Aufruf: $0 {set|verify|off|map} (set liest TP25_PINSPEC)" >&2; exit 2 ;;
esac

View File

@@ -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]*='
}

View File

@@ -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-<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)"
@@ -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-<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 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. <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 (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