TP25: Raumkarte -- der Dead Spot ist die Terrasse, das Haus ist sauber

Zweiter Gehweg-Durchgang, diesmal methodisch sauber: Ping vom Core statt vom
Laptop, Konsole auf dem iPhone (Wake Lock haelt es wach, sonst misst man
Ruhezustand), Raumwechsel per Klick markiert.

Das Ergebnis ist eindeutig und macht die Roaming-Geschichte klein. Im ganzen Haus
null Paketverlust -- Technik UG und OG, Diele, Kinder-, Schlaf- und
Arbeitszimmer, Treppe, alle 0 %, und die Uebergaben ap1<->ap2 auf der Treppe
kosten 0,2 s. Auf der Terrasse dagegen 43 % Verlust ueber 162 Sekunden, am Stueck
fehlen 20,4 Sekunden, bei -72 dBm. Dort ist ein Call nicht gestoert, sondern weg.

Die Topologie erklaert es vollstaendig: ap2 steht in Technik OG, das nur ueber
das Arbeitszimmer zugaenglich ist -- zur Terrasse liegen Technik OG,
Arbeitszimmer, Diele OG und die Aussenwand dazwischen.

Das dreht die Massnahmen um. Ein signal-range-Boden (Fund E) wuerde die Terrasse
VERSCHLECHTERN, weil es dort keinen besseren AP gibt, auf den er steuern koennte
-- er wuerde nur rauswerfen. Stattdessen: 5 GHz auf 20 MHz (+3 dB, ein Wort im
Template), dann ap2 von Technik OG in die Diele OG umhaengen, die an Terrasse,
Arbeitszimmer und Treppe grenzt. Kein neuer AP noetig, damit ist auch die
User-Randbedingung erfuellt.

Konsole nachgezogen: Marker werden URL-dekodiert und behalten ihre Gross- und
Kleinschreibung (aus "RAUM=Diele OG" wurde "RAUM%3DDIELE%20OG"), Diele OG ist
ergaenzt, und das Raumraster steht jetzt oben -- ohne JavaScript laedt die Seite
nach jedem Klick neu, und dann muss der naechste Raum ohne Scrollen erreichbar
sein.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 15:43:33 +02:00
parent 66035c3166
commit 82c8f0e828
6 changed files with 3594 additions and 8 deletions

View File

@@ -21,6 +21,7 @@ Aendert nichts am Netz. Bindet auf 0.0.0.0 (Bedienung vom iPad).
import json
import os
import signal
import urllib.parse
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -36,7 +37,7 @@ PORT = int(os.environ.get("TP6_PORT", "3031"))
# Aenderbar ohne Codeeingriff: TP25_ROOMS="Kueche,Bad,..." python3 docs/tp6-marker/server.py
ROOMS = [r.strip() for r in os.environ.get(
"TP25_ROOMS",
"Küche,Wohnzimmer,Diele UG,Technik UG,Treppe,Kinderzimmer,Schlafzimmer,Arbeitszimmer,Bad,Klo,Technik OG,Terrasse"
"Küche,Wohnzimmer,Diele UG,Technik UG,Treppe,Kinderzimmer,Schlafzimmer,Diele OG,Arbeitszimmer,Bad,Klo,Technik OG,Terrasse"
).split(",") if r.strip()]
MARKERS = os.path.join(RUN_DIR, "markers.csv")
@@ -198,7 +199,7 @@ def render(st):
raumzeile = ('<div class="card"><div class="phase">Wo bist du gerade?</div>'
'<div class="inst">%s</div><div class="raster">%s</div></div>' % (aktuell, kacheln))
btns = raumzeile + "\n".join(
btns = "\n".join(
'<form method="post" action="/m?type=%s" onsubmit="return send(event,\'%s\')">'
'<button class="%s" type="submit">%s</button></form>' % (n, n, c, l)
for n, l, c, modes in BUTTONS if mode in modes
@@ -207,7 +208,7 @@ def render(st):
return PAGE.format(
banner=banner, title=st.get("title", "—"), phase=st.get("phase", "—"),
instruction=st.get("instruction", ""), prog=prog, countdown=countdown,
wlan=wlan, wan="rx %s / tx %s · CPU %s%%" % (fmt_bps(w.get("wan_rx")),
raumzeile=raumzeile, wlan=wlan, wan="rx %s / tx %s · CPU %s%%" % (fmt_bps(w.get("wan_rx")),
fmt_bps(w.get("wan_tx")), w.get("cpu", "?")),
last=last_marker(), buttons=btns,
)
@@ -249,6 +250,7 @@ form{{margin:0}}
{countdown}
{prog}
</div>
{raumzeile}
<div class="live">WLAN {wlan}
WAN {wan}
Marker {last}</div>
@@ -331,7 +333,11 @@ class Handler(BaseHTTPRequestHandler):
self._send(200, "{}")
return
if self.path.startswith("/m?"):
name = self.path.split("type=", 1)[1].split("&")[0].upper()
# 🔴 Erst dekodieren, dann NICHT hochschreiben: "RAUM%3DDiele%20OG" wurde sonst zu
# "RAUM%3DDIELE%20OG" — der Marker war weder lesbar noch als Raum erkennbar.
name = urllib.parse.unquote(self.path.split("type=", 1)[1].split("&")[0])
if not name.startswith("RAUM="):
name = name.upper()
if name == "ABORT":
write_marker("ABORT_killed=%d" % kill_flows())
else: