Files
platformio_fensterpiepser_e…/architecture.html
T

493 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Fensterpiepser ESP32 Architekturübersicht</title>
<style>
:root {
--bg: #0f1117;
--panel: #1a1d27;
--border: #2e3248;
--accent: #4f8ef7;
--green: #3ecf8e;
--orange: #f7964f;
--red: #f75f5f;
--purple: #b57bee;
--yellow: #f7d44f;
--text: #d4d8f0;
--muted: #6b7194;
--mono: 'Consolas', 'Courier New', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: var(--mono); font-size: 13px; padding: 24px; }
h1 { font-size: 20px; color: var(--accent); margin-bottom: 4px; }
h2 { font-size: 14px; color: var(--orange); margin: 28px 0 10px; text-transform: uppercase; letter-spacing: 1px; }
.subtitle { color: var(--muted); font-size: 11px; margin-bottom: 24px; }
svg text { font-family: var(--mono); }
table { border-collapse: collapse; width: 100%; margin-bottom: 8px; }
th { background: #22263a; color: var(--orange); font-weight: normal; text-align: left; padding: 5px 10px; border: 1px solid var(--border); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
td { padding: 5px 10px; border: 1px solid var(--border); vertical-align: top; }
tr:nth-child(even) td { background: #161924; }
.tag { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; margin: 1px; }
.t-isr { background:#3a1a1a; color:var(--red); border:1px solid var(--red); }
.t-task { background:#1a2a3a; color:var(--accent); border:1px solid var(--accent); }
.t-loop { background:#1a3a2a; color:var(--green); border:1px solid var(--green); }
.t-hw { background:#2a2a1a; color:var(--yellow); border:1px solid var(--yellow); }
.t-net { background:#2a1a3a; color:var(--purple); border:1px solid var(--purple); }
.col-core0 { color: var(--accent); }
.col-core1 { color: var(--green); }
.col-isr { color: var(--red); }
.col-hw { color: var(--yellow); }
.col-net { color: var(--purple); }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
section { margin-bottom: 32px; }
</style>
</head>
<body>
<h1>Fensterpiepser ESP32 &mdash; Architekturübersicht</h1>
<div class="subtitle">NodeMCU-32S &nbsp;·&nbsp; Arduino/ESP-IDF &nbsp;·&nbsp; v2.0.8 &nbsp;·&nbsp; Varianten: EG / DG</div>
<!-- ═══════════════════════════════════════════════════════════
1. DUAL-CORE / TASK ÜBERSICHT
═══════════════════════════════════════════════════════════ -->
<section>
<h2>1 · Tasks &amp; Interrupts Gesamtübersicht</h2>
<svg viewBox="0 0 960 460" width="100%" style="max-width:960px;display:block;">
<defs>
<marker id="arr" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0,8 3,0 6" fill="#4f8ef7"/>
</marker>
<marker id="arr-g" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0,8 3,0 6" fill="#3ecf8e"/>
</marker>
<marker id="arr-o" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0,8 3,0 6" fill="#f7964f"/>
</marker>
<marker id="arr-r" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0,8 3,0 6" fill="#f75f5f"/>
</marker>
<marker id="arr-p" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0 0,8 3,0 6" fill="#b57bee"/>
</marker>
</defs>
<!-- ESP32 chip outline -->
<rect x="10" y="10" width="940" height="440" rx="10" fill="#12151f" stroke="#2e3248" stroke-width="1.5"/>
<text x="480" y="32" text-anchor="middle" fill="#2e3248" font-size="13" font-weight="bold">ESP32 NodeMCU-32S · 240 MHz · Xtensa LX6 Dual-Core</text>
<!-- ── Core 0 ── -->
<rect x="30" y="44" width="420" height="390" rx="8" fill="#14192a" stroke="#4f8ef7" stroke-width="1.2"/>
<text x="240" y="64" text-anchor="middle" fill="#4f8ef7" font-size="12" font-weight="bold">Core 0</text>
<!-- beepTask -->
<rect x="50" y="76" width="380" height="90" rx="6" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="70" y="96" fill="#4f8ef7" font-size="12" font-weight="bold">beepTask</text>
<text x="380" y="96" text-anchor="end" fill="#6b7194" font-size="10">Prio 5 · Core 0 · Stack 2 KB</text>
<text x="70" y="114" fill="#d4d8f0" font-size="10">· Wartet auf Task Notification (gegeben alle 10 ms von timerIsr)</text>
<text x="70" y="129" fill="#d4d8f0" font-size="10">· Short-Beep-Timing (30 ms Puls)</text>
<text x="70" y="144" fill="#d4d8f0" font-size="10">· PiepPattern::TaskCyclic() → PiepMode → toneRequest → GPIO18</text>
<!-- rfDecodeTask -->
<rect x="50" y="178" width="380" height="80" rx="6" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="70" y="198" fill="#4f8ef7" font-size="12" font-weight="bold">rfDecodeTask</text>
<text x="380" y="198" text-anchor="end" fill="#6b7194" font-size="10">Prio 4 · Core 0 · Stack 3 KB</text>
<text x="70" y="216" fill="#d4d8f0" font-size="10">· Wartet auf rfEventQueue (RfEvent_t: dtMicros, pinValue)</text>
<text x="70" y="231" fill="#d4d8f0" font-size="10">· receiverSw / Aiggend / Oval / Kerui / Thermo .Isr()</text>
<text x="70" y="246" fill="#d4d8f0" font-size="10">· Setzt lastReceivedKeyNum / bNewData für loop</text>
<!-- WiFi stack -->
<rect x="50" y="270" width="380" height="60" rx="6" fill="#1a1a30" stroke="#2e3248" stroke-width="1" stroke-dasharray="4,3"/>
<text x="70" y="292" fill="#6b7194" font-size="12" font-weight="bold">WiFi / TCP-IP Stack</text>
<text x="380" y="292" text-anchor="end" fill="#6b7194" font-size="10">ESP-IDF intern</text>
<text x="70" y="310" fill="#6b7194" font-size="10">· Autonome Tasks (pro_cpu_0) · PubSubClient nutzt WiFiClient</text>
<text x="70" y="323" fill="#6b7194" font-size="10">· SSID: Elektrosmog · Broker: flokke.de:1883</text>
<!-- NTP / Hostname -->
<rect x="50" y="342" width="380" height="74" rx="6" fill="#1a1a2a" stroke="#2e3248" stroke-width="1" stroke-dasharray="4,3"/>
<text x="70" y="362" fill="#6b7194" font-size="11">NTP: ptbtime3.ptb.de (GMT+1 / DST+1)</text>
<text x="70" y="378" fill="#6b7194" font-size="11">Hostname: FENSTER_PIEPSER_NODEMCU_32_S_EG</text>
<text x="70" y="394" fill="#6b7194" font-size="11">OTA: WebServer Port 80 (/update)</text>
<text x="70" y="409" fill="#6b7194" font-size="11">Reset nach 24 h (wenn alle Fenster zu)</text>
<!-- ── Core 1 ── -->
<rect x="510" y="44" width="420" height="390" rx="8" fill="#14211a" stroke="#3ecf8e" stroke-width="1.2"/>
<text x="720" y="64" text-anchor="middle" fill="#3ecf8e" font-size="12" font-weight="bold">Core 1</text>
<!-- timerIsr -->
<rect x="530" y="76" width="380" height="75" rx="6" fill="#2a1a1a" stroke="#f75f5f" stroke-width="1"/>
<text x="550" y="96" fill="#f75f5f" font-size="12" font-weight="bold">timerIsr</text>
<text x="900" y="96" text-anchor="end" fill="#6b7194" font-size="10">hw_timer 0 · 1 MHz · Alarm 10 000 µs</text>
<text x="550" y="114" fill="#d4d8f0" font-size="10">· ++count; if count==10 → tDecis++ (volatile)</text>
<text x="550" y="129" fill="#d4d8f0" font-size="10">· vTaskNotifyGiveFromISR(beepTaskHandle) + portYIELD_FROM_ISR</text>
<text x="550" y="144" fill="#6b7194" font-size="10"> ← gesamte Beep-Logik jetzt in beepTask</text>
<!-- pinLevelChangeIsr -->
<rect x="530" y="163" width="380" height="75" rx="6" fill="#2a1a1a" stroke="#f75f5f" stroke-width="1"/>
<text x="550" y="183" fill="#f75f5f" font-size="12" font-weight="bold">pinLevelChangeIsr</text>
<text x="900" y="183" text-anchor="end" fill="#6b7194" font-size="10">GPIO 26 · CHANGE · INPUT_PULLUP</text>
<text x="550" y="201" fill="#d4d8f0" font-size="10">· dtMicros = micros() lastMics · pinVal = digitalRead(26)</text>
<text x="550" y="216" fill="#d4d8f0" font-size="10">· Filter: loopCountSincePause &lt; 2 || dtMicros &lt; 100 µs → skip</text>
<text x="550" y="231" fill="#d4d8f0" font-size="10">· xQueueSendFromISR(rfEventQueue, &amp;ev) + portYIELD_FROM_ISR</text>
<!-- loopTask -->
<rect x="530" y="250" width="380" height="174" rx="6" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="550" y="270" fill="#3ecf8e" font-size="12" font-weight="bold">loopTask (Arduino loop)</text>
<text x="900" y="270" text-anchor="end" fill="#6b7194" font-size="10">Prio 1 · Core 1 · vTaskDelay 5 ms</text>
<text x="550" y="289" fill="#d4d8f0" font-size="10">1 handleKeyReceived() Empfangene Keys → Fenster-State → MQTT</text>
<text x="550" y="304" fill="#d4d8f0" font-size="10">2 handleDispUpdate() SPI-Display (1 s / 10 s Interval)</text>
<text x="550" y="319" fill="#d4d8f0" font-size="10">3 handleWifiConnection() Reconnect nach 30 s, Restart on Timeout</text>
<text x="550" y="334" fill="#d4d8f0" font-size="10">4 myMqttClient.onLoop() MQTT keepalive, RSSI alle 600 s</text>
<text x="550" y="349" fill="#d4d8f0" font-size="10">5 UpdateHandler() HTTP OTA (WebServer Port 80)</text>
<text x="550" y="364" fill="#d4d8f0" font-size="10">6 updatePiepPattern() Pause-Modus nach 6 min Dauerbeeep</text>
<text x="550" y="379" fill="#d4d8f0" font-size="10">7 xcpLoop() XCP-Events 10 / 100 / 500 / 1000 ms</text>
<text x="550" y="394" fill="#d4d8f0" font-size="10">8 loopCountSincePause++ RF-Debounce Zähler</text>
<text x="550" y="409" fill="#d4d8f0" font-size="10">9 vTaskDelay(5 ms) gibt Core 1 für ISRs frei</text>
<!-- ── FreeRTOS Primitives ── -->
<!-- Task Notification arrow: timerIsr → beepTask -->
<line x1="530" y1="108" x2="432" y2="108" stroke="#4f8ef7" stroke-width="1.5" marker-end="url(#arr)" stroke-dasharray="5,3"/>
<rect x="436" y="98" width="90" height="18" rx="3" fill="#0f1117"/>
<text x="481" y="111" text-anchor="middle" fill="#4f8ef7" font-size="10">Task Notification</text>
<!-- rfEventQueue arrow: pinLevelChangeIsr → rfDecodeTask -->
<line x1="530" y1="210" x2="432" y2="218" stroke="#4f8ef7" stroke-width="1.5" marker-end="url(#arr)" stroke-dasharray="5,3"/>
<rect x="438" y="207" width="82" height="18" rx="3" fill="#0f1117"/>
<text x="479" y="220" text-anchor="middle" fill="#4f8ef7" font-size="10">rfEventQueue[8]</text>
<!-- receiver state arrow: rfDecodeTask → loop (dashed green) -->
<line x1="430" y1="242" x2="530" y2="310" stroke="#3ecf8e" stroke-width="1" marker-end="url(#arr-g)" stroke-dasharray="4,3"/>
<text x="471" y="284" text-anchor="middle" fill="#3ecf8e" font-size="10" transform="rotate(-27,471,284)">keyNum / dir</text>
</svg>
</section>
<!-- ═══════════════════════════════════════════════════════════
2. FreeRTOS PRIMITIVE
═══════════════════════════════════════════════════════════ -->
<section>
<h2>2 · FreeRTOS Synchronisationsobjekte</h2>
<table>
<tr><th>Objekt</th><th>Typ</th><th>Größe</th><th>Produzent (ISR)</th><th>Konsument (Task)</th><th>Zweck</th></tr>
<tr>
<td><span class="tag t-task">beepTaskHandle</span></td>
<td>Task Notification</td><td>32-bit Zähler im TCB</td>
<td><span class="tag t-isr">timerIsr</span> alle 10 ms<br><code style="font-size:10px">vTaskNotifyGiveFromISR</code></td>
<td><span class="tag t-task">beepTask</span> Core 0, Prio 5<br><code style="font-size:10px">ulTaskNotifyTake(pdTRUE, …)</code></td>
<td>Takt für Beep-State-Machine. Kein Heap-Objekt nötig, schneller als Semaphore. pdTRUE = Zähler nach Take auf 0 → verhält sich wie Binary Semaphore.</td>
</tr>
<tr>
<td><span class="tag t-task">rfEventQueue</span></td>
<td>Queue RfEvent_t</td><td>8 Einträge × 5 B</td>
<td><span class="tag t-isr">pinLevelChangeIsr</span> bei jeder Flanke<br><code style="font-size:10px">xQueueSendFromISR</code></td>
<td><span class="tag t-task">rfDecodeTask</span> Core 0, Prio 4<br><code style="font-size:10px">xQueueReceive</code></td>
<td>Überträgt {dtMicros, pinValue}. dtMicros wird in der ISR gemessen und by-value kopiert — Task decodiert den gespeicherten Wert, kein erneutes micros() nötig. Queue statt Notification, weil Nutzdaten übertragen werden.</td>
</tr>
</table>
<div style="margin-top:14px;">
<div style="color:var(--orange);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;">Wann Semaphore statt Task Notification?</div>
<table>
<tr><th>Situation</th><th>Grund</th></tr>
<tr><td>Mehrere Tasks warten auf dasselbe Ereignis</td><td>Task Notification ist 1:1 — ein Handle, ein Empfänger</td></tr>
<tr><td>Mutex / gegenseitiger Ausschluss</td><td><code>xSemaphoreCreateMutex()</code> mit Priority Inheritance; Notification hat keine Mutex-Semantik</td></tr>
<tr><td>Produzent soll Feedback erhalten wenn voll</td><td>Counting Semaphore meldet <code>errQUEUE_FULL</code>; Notification-Zähler läuft lautlos über</td></tr>
<tr><td>Signalgeber kennt Empfänger nicht</td><td>Semaphore ist ein eigenständiges Objekt das man übergeben kann; Notification braucht <code>TaskHandle_t</code></td></tr>
</table>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
3. SIGNAL-FLÜSSE
═══════════════════════════════════════════════════════════ -->
<section>
<h2>3 · Signalflüsse</h2>
<svg viewBox="0 0 960 300" width="100%" style="max-width:960px;display:block;">
<defs>
<marker id="a1" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto"><polygon points="0 0,7 2.5,0 5" fill="#f7964f"/></marker>
<marker id="a2" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto"><polygon points="0 0,7 2.5,0 5" fill="#3ecf8e"/></marker>
<marker id="a3" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto"><polygon points="0 0,7 2.5,0 5" fill="#b57bee"/></marker>
</defs>
<!-- ── RF-Empfangspfad ── -->
<text x="20" y="28" fill="#f7964f" font-size="11" font-weight="bold">RF-Empfangspfad (433 MHz OOK)</text>
<!-- boxes -->
<rect x="20" y="38" width="100" height="40" rx="5" fill="#2a1a0a" stroke="#f7964f" stroke-width="1"/>
<text x="70" y="54" text-anchor="middle" fill="#f7964f" font-size="10">433 MHz</text>
<text x="70" y="68" text-anchor="middle" fill="#f7964f" font-size="10">Empfänger</text>
<rect x="150" y="38" width="110" height="40" rx="5" fill="#2a1a1a" stroke="#f75f5f" stroke-width="1"/>
<text x="205" y="54" text-anchor="middle" fill="#f75f5f" font-size="10">pinLevelChange</text>
<text x="205" y="68" text-anchor="middle" fill="#f75f5f" font-size="10">Isr (Core 1)</text>
<rect x="290" y="38" width="100" height="40" rx="5" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="340" y="54" text-anchor="middle" fill="#4f8ef7" font-size="10">rfEventQueue</text>
<text x="340" y="68" text-anchor="middle" fill="#6b7194" font-size="10">32 × RfEvent_t</text>
<rect x="420" y="38" width="100" height="40" rx="5" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="470" y="54" text-anchor="middle" fill="#4f8ef7" font-size="10">rfDecodeTask</text>
<text x="470" y="68" text-anchor="middle" fill="#6b7194" font-size="10">Core 0 Prio 4</text>
<rect x="550" y="20" width="115" height="18" rx="3" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="607" y="33" text-anchor="middle" fill="#3ecf8e" font-size="10">ReceiverSmartWares</text>
<rect x="550" y="42" width="115" height="18" rx="3" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="607" y="55" text-anchor="middle" fill="#3ecf8e" font-size="10">ReceiverAiggend</text>
<rect x="550" y="64" width="115" height="18" rx="3" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="607" y="77" text-anchor="middle" fill="#3ecf8e" font-size="10">ReceiverOval</text>
<rect x="550" y="86" width="115" height="18" rx="3" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="607" y="99" text-anchor="middle" fill="#3ecf8e" font-size="10">ReceiverKerui</text>
<rect x="550" y="108" width="115" height="18" rx="3" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="607" y="121" text-anchor="middle" fill="#3ecf8e" font-size="10">ReceiverFunkThermo</text>
<rect x="695" y="38" width="100" height="40" rx="5" fill="#1a2a1a" stroke="#3ecf8e" stroke-width="1"/>
<text x="745" y="54" text-anchor="middle" fill="#3ecf8e" font-size="10">loopTask</text>
<text x="745" y="68" text-anchor="middle" fill="#6b7194" font-size="10">handleKey</text>
<rect x="825" y="38" width="110" height="40" rx="5" fill="#2a1a3a" stroke="#b57bee" stroke-width="1"/>
<text x="880" y="54" text-anchor="middle" fill="#b57bee" font-size="10">MQTT publish</text>
<text x="880" y="68" text-anchor="middle" fill="#6b7194" font-size="10">flokke.de:1883</text>
<!-- arrows RF path -->
<line x1="120" y1="58" x2="148" y2="58" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="260" y1="58" x2="288" y2="58" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="390" y1="58" x2="418" y2="58" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="520" y1="50" x2="548" y2="30" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="520" y1="55" x2="548" y2="51" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="520" y1="58" x2="548" y2="73" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="520" y1="62" x2="548" y2="95" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="520" y1="66" x2="548" y2="117" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="665" y1="58" x2="693" y2="58" stroke="#3ecf8e" stroke-width="1.5" marker-end="url(#a2)"/>
<line x1="795" y1="58" x2="823" y2="58" stroke="#b57bee" stroke-width="1.5" marker-end="url(#a3)"/>
<!-- ── Beep-Pfad ── -->
<text x="20" y="165" fill="#f7964f" font-size="11" font-weight="bold">Beep-Pfad (Buzzer-Ansteuerung)</text>
<rect x="20" y="175" width="100" height="40" rx="5" fill="#2a1a1a" stroke="#f75f5f" stroke-width="1"/>
<text x="70" y="191" text-anchor="middle" fill="#f75f5f" font-size="10">timerIsr</text>
<text x="70" y="205" text-anchor="middle" fill="#6b7194" font-size="10">10 ms · Core 1</text>
<rect x="150" y="175" width="100" height="40" rx="5" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="200" y="191" text-anchor="middle" fill="#4f8ef7" font-size="10">Task Notification</text>
<text x="200" y="205" text-anchor="middle" fill="#6b7194" font-size="10">im TCB (kein Objekt)</text>
<rect x="280" y="175" width="100" height="40" rx="5" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="330" y="191" text-anchor="middle" fill="#4f8ef7" font-size="10">beepTask</text>
<text x="330" y="205" text-anchor="middle" fill="#6b7194" font-size="10">Core 0 Prio 5</text>
<rect x="410" y="175" width="100" height="40" rx="5" fill="#1a2240" stroke="#4f8ef7" stroke-width="1"/>
<text x="460" y="191" text-anchor="middle" fill="#4f8ef7" font-size="10">PiepPattern</text>
<text x="460" y="205" text-anchor="middle" fill="#6b7194" font-size="10">TaskCyclic()</text>
<rect x="540" y="157" width="90" height="18" rx="3" fill="#1a2040" stroke="#4f8ef7" stroke-width="1"/>
<text x="585" y="170" text-anchor="middle" fill="#4f8ef7" font-size="10">piepFast ×2</text>
<rect x="540" y="179" width="90" height="18" rx="3" fill="#1a2040" stroke="#4f8ef7" stroke-width="1"/>
<text x="585" y="192" text-anchor="middle" fill="#4f8ef7" font-size="10">piepSlow ×1</text>
<rect x="540" y="201" width="90" height="18" rx="3" fill="#1a2040" stroke="#4f8ef7" stroke-width="1"/>
<text x="585" y="214" text-anchor="middle" fill="#4f8ef7" font-size="10">piepPause ×120</text>
<rect x="660" y="175" width="100" height="40" rx="5" fill="#2a2a1a" stroke="#f7d44f" stroke-width="1"/>
<text x="710" y="191" text-anchor="middle" fill="#f7d44f" font-size="10">toneRequest</text>
<text x="710" y="205" text-anchor="middle" fill="#6b7194" font-size="10">bitmask</text>
<rect x="790" y="175" width="100" height="40" rx="5" fill="#2a2a1a" stroke="#f7d44f" stroke-width="1"/>
<text x="840" y="191" text-anchor="middle" fill="#f7d44f" font-size="10">GPIO 18</text>
<text x="840" y="205" text-anchor="middle" fill="#6b7194" font-size="10">PIEP_VCC</text>
<rect x="910" y="175" width="35" height="40" rx="5" fill="#1a1a00" stroke="#f7d44f" stroke-width="1"/>
<text x="928" y="199" text-anchor="middle" fill="#f7d44f" font-size="20">🔔</text>
<!-- arrows beep path -->
<line x1="120" y1="195" x2="148" y2="195" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="250" y1="195" x2="278" y2="195" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="380" y1="195" x2="408" y2="195" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="510" y1="188" x2="538" y2="167" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="510" y1="193" x2="538" y2="188" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="510" y1="198" x2="538" y2="210" stroke="#f7964f" stroke-width="1" marker-end="url(#a1)"/>
<line x1="630" y1="195" x2="658" y2="195" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="760" y1="195" x2="788" y2="195" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<line x1="890" y1="195" x2="908" y2="195" stroke="#f7964f" stroke-width="1.5" marker-end="url(#a1)"/>
<!-- PWM note -->
<text x="840" y="240" text-anchor="middle" fill="#6b7194" font-size="9">GPIO33 PIEP_PWM</text>
<text x="840" y="252" text-anchor="middle" fill="#6b7194" font-size="9">2 kHz · LEDC Ch12</text>
<text x="840" y="264" text-anchor="middle" fill="#6b7194" font-size="9">duty 127/255 (fix)</text>
<!-- bRequestShortBeep note -->
<text x="310" y="240" text-anchor="middle" fill="#6b7194" font-size="9">bRequestShortBeep (volatile)</text>
<text x="310" y="252" text-anchor="middle" fill="#6b7194" font-size="9">gesetzt von loopTask</text>
<text x="310" y="264" text-anchor="middle" fill="#6b7194" font-size="9">→ 30 ms Bestätigungs-Piep</text>
</svg>
</section>
<!-- ═══════════════════════════════════════════════════════════
4. RECEIVER-ÜBERSICHT
═══════════════════════════════════════════════════════════ -->
<section>
<h2>4 · 433-MHz-Receiver</h2>
<table>
<tr><th>Objekt</th><th>Protokoll</th><th>Key-Bytes</th><th>Anzahl Keys</th><th>Key-Index-Offset</th><th>Basisklasse</th></tr>
<tr><td>receiverSw</td><td>SmartWares Manchester OOK</td><td>8 B</td><td>22</td><td>0</td><td>Receiver433</td></tr>
<tr><td>receiverAiggend</td><td>3-Byte-Key PWM</td><td>3 B + 1 Richtungs-B</td><td>konfigurierbar</td><td>+30</td><td>Receiver3ByteKey</td></tr>
<tr><td>receiverOval</td><td>3-Byte-Key PWM</td><td>3 B + 1 Richtungs-B</td><td>konfigurierbar</td><td>+40</td><td>Receiver3ByteKey</td></tr>
<tr><td>receiverKerui</td><td>3-Byte-Key PWM</td><td>3 B + 1 Richtungs-B</td><td>konfigurierbar</td><td>+50</td><td>Receiver3ByteKey</td></tr>
<tr><td>receiverThermo</td><td>Funk-Thermometer PWM</td><td>4 B (36 bit, 4 bit ignoriert)</td><td></td><td></td><td>Receiver433</td></tr>
</table>
<div style="color:var(--muted);font-size:11px;margin-top:6px;">
Alle .Isr()-Aufrufe erfolgen in <span class="tag t-task">rfDecodeTask</span> (Core 0) via rfEventQueue.
Debounce: loopCountSincePause &lt; 2 → ISR verwirft Flanke nach Key-Empfang.
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
5. FENSTER-OBJEKTE
═══════════════════════════════════════════════════════════ -->
<section>
<h2>5 · Fenster-Objekte</h2>
<div class="grid2">
<div>
<div style="color:var(--accent);font-size:11px;margin-bottom:6px;">Variante EG (VERSION_DG_ONLY=0, alleFenster[4])</div>
<table>
<tr><th>Name</th><th>Sensor-Typ</th><th>Key-Nr (mit Offset)</th><th>Wait-Zeit-Quelle</th></tr>
<tr><td>fensterWz</td><td>Kerui</td><td>2 + 50 = 52</td><td>waitTimeDs (Temperatur)</td></tr>
<tr><td>fensterBadUnten</td><td>Aiggend</td><td>3 + 30 = 33</td><td>waitTimeDs</td></tr>
<tr><td>fensterKueche</td><td>Oval</td><td>2 + 40 = 42</td><td>waitTimeDs</td></tr>
<tr><td>fensterToniNeu</td><td>Oval</td><td>1 + 40 = 41</td><td>waitTimeDs</td></tr>
</table>
</div>
<div>
<div style="color:var(--accent);font-size:11px;margin-bottom:6px;">Variante DG (VERSION_DG_ONLY=1, alleFenster[4])</div>
<table>
<tr><th>Name</th><th>Sensor-Typ</th><th>Key-Nr (mit Offset)</th><th>Wait-Zeit-Quelle</th></tr>
<tr><td>fensterBadOben</td><td>Aiggend</td><td>1 + 30 = 31</td><td>waitTimeDs</td></tr>
<tr><td>fensterFlo</td><td>Aiggend</td><td>2 + 30 = 32</td><td>waitTimeDs</td></tr>
<tr><td>fensterHanna</td><td>Oval</td><td>5 + 40 = 45</td><td>waitTimeDs</td></tr>
<tr><td>fensterFloBuero</td><td>Oval</td><td>4 + 40 = 44</td><td>waitTimeDs</td></tr>
</table>
</div>
</div>
<div style="margin-top:10px;">
<table>
<tr><th colspan="4">Sonder-Objekte (immer instanziiert)</th></tr>
<tr><th>Name</th><th>Sensor-Typ</th><th>Key-Nr</th><th>Besonderheit</th></tr>
<tr><td>fensterGefrier</td><td>SmartWares</td><td>10</td><td>Feste Wait-Zeit: 30 s (hardcodiert in ShallPiep())</td></tr>
</table>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
6. BEEP-MUSTER
═══════════════════════════════════════════════════════════ -->
<section>
<h2>6 · Beep-Muster (PiepPattern)</h2>
<table>
<tr><th>Modus</th><th>onTime</th><th>offTime</th><th>requestCount</th><th>Reihenfolge</th></tr>
<tr><td>piepFast</td><td>20 ms</td><td>80 ms</td><td>2 Mal</td><td>1.</td></tr>
<tr><td>piepSlow</td><td>500 ms</td><td>100 ms</td><td>1 Mal</td><td>2.</td></tr>
<tr><td>piepPauseLong</td><td>0 ms</td><td>1000 ms</td><td>120 × = 60 s</td><td>3. (Standardpause)</td></tr>
<tr><td>piepPauseShort</td><td>0 ms</td><td>1000 ms</td><td>20 × = 10 s</td><td>3. (nach 6 min Dauerbeeep)</td></tr>
</table>
<div style="color:var(--muted);font-size:11px;margin-top:6px;">
Pattern läuft zyklisch: fast → slow → pause(Long/Short) → fast → …&nbsp;&nbsp;
Start/Stop über piepPattern→startRequest / stopRequest (volatile).&nbsp;&nbsp;
Stopp bei Nacht (bIsNightTime) oder alle Fenster zu.
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
7. WAITTIME (Temperaturabhängige Alarmverzögerung)
═══════════════════════════════════════════════════════════ -->
<section>
<h2>7 · Wartezeit-Berechnung (waitTimeDs)</h2>
<table>
<tr><th>Außentemperatur</th><th>Wartezeit</th><th>Erklärung</th></tr>
<tr><td>&lt; 5 °C</td><td>5 min</td><td>Kurz kalte Luft, schnelles Lüften</td></tr>
<tr><td>5 … 19,5 °C</td><td>5 … 30 min (linear)</td><td>Lineare Interpolation: 5 + (T+5)×25/24,5</td></tr>
<tr><td>19,5 … 25 °C</td><td>~2 Wochen</td><td>Kein Alarm Sommer, Lüften sinnvoll</td></tr>
<tr><td>&gt; 25 °C</td><td>30 min</td><td>Heiß kurze Alarmverzögerung</td></tr>
<tr><td>kein Sensor-Wert</td><td>10 min</td><td>Default bis erste Thermo-Meldung</td></tr>
</table>
<div style="color:var(--muted);font-size:11px;margin-top:6px;">
Quelle: receiverThermo (Funk-Thermometer, Aussensensor) · Update bei jedem neuen Thermo-Paket (~50 s Intervall).
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
8. GPIO-BELEGUNG
═══════════════════════════════════════════════════════════ -->
<section>
<h2>8 · GPIO-Belegung</h2>
<div class="grid2">
<table>
<tr><th>GPIO</th><th>Richtung</th><th>Funktion</th></tr>
<tr><td>26</td><td>INPUT_PULLUP</td><td>DIN_FUNK 433-MHz-Datensignal → pinLevelChangeIsr</td></tr>
<tr><td>12</td><td>OUTPUT HIGH</td><td>DOUT_FUNK_VCC Versorgung Empfängermodul</td></tr>
<tr><td>32</td><td>OUTPUT LOW</td><td>DOUT_FUNK_GND GND Empfängermodul</td></tr>
<tr><td>18</td><td>OUTPUT</td><td>DOUT_PIEP_VCC Buzzer-Versorgung (geschaltet via toneRequest)</td></tr>
<tr><td>33</td><td>LEDC PWM</td><td>DOUT_PIEP_PWM Buzzer-PWM 2 kHz, Ch 12, duty 127</td></tr>
<tr><td>16</td><td>OUTPUT HIGH</td><td>DOUT_TEST_TIMER Debug-Pin</td></tr>
</table>
<table>
<tr><th>GPIO</th><th>Funktion</th><th>Interface</th></tr>
<tr><td>14</td><td>TFT_PIN_CLK (SCK)</td><td rowspan="5" style="vertical-align:middle">SPI Adafruit ST7735<br>(VERSION_BIG_DISPLAY)</td></tr>
<tr><td>13</td><td>TFT_PIN_MOSI (SDA)</td></tr>
<tr><td>22</td><td>TFT_PIN_RST (RES)</td></tr>
<tr><td>21</td><td>TFT_PIN_DC (RS)</td></tr>
<tr><td>15</td><td>TFT_PIN_CS</td></tr>
</table>
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
9. MQTT-TOPICS
═══════════════════════════════════════════════════════════ -->
<section>
<h2>9 · MQTT-Topics (Broker: flokke.de:1883)</h2>
<table>
<tr><th>Topic</th><th>Retain</th><th>Wann</th><th>Payload (Beispiel)</th></tr>
<tr><td>fenster/system/EG</td><td>nein</td><td>Connect, Reset</td><td>{"e":"hello","client":"FensterpiepserEG_v2.0.8"}</td></tr>
<tr><td>fenster/status/EG</td><td>ja</td><td>Fenster bewegt / Beep gestartet</td><td>{"t":1700000000,"e":"move","n":4,"f":["Wohnz",…],"o":[0,1,…],"b":[0,0,…]}</td></tr>
<tr><td>fenster/temprh/EG</td><td>ja</td><td>Neues Thermo-Paket (~50 s)</td><td>{"T":7,"rh":65,"t":1700000000,"raw":3422534}</td></tr>
<tr><td>fenster/rssi/EG</td><td>nein</td><td>Alle 600 s</td><td>{"rssi":-62}</td></tr>
</table>
<div style="color:var(--muted);font-size:11px;margin-top:6px;">
LWT (Will) auf fenster/temprh/EG: {"T":0,"rh":0,"t":0} · User: lightcontrol · Keepalive: 600 s
</div>
</section>
<!-- ═══════════════════════════════════════════════════════════
10. VOLATILE / SHARED STATE
═══════════════════════════════════════════════════════════ -->
<section>
<h2>10 · Geteilte Variablen &amp; volatile-Annotierungen</h2>
<table>
<tr><th>Variable</th><th>Typ</th><th>volatile</th><th>Schreiber</th><th>Leser</th><th>Anmerkung</th></tr>
<tr><td>tDecis</td><td>uint32_t</td><td></td><td>timerIsr (Core 1)</td><td>loopTask, beepTask, Fenster::ShallPiep</td><td>Monoton steigend, 32-bit-Reads atomar auf LX6</td></tr>
<tr><td>bRequestShortBeep</td><td>bool</td><td></td><td>loopTask (Core 1)</td><td>beepTask (Core 0)</td><td>Cross-Core; single-byte write atomar</td></tr>
<tr><td>loopCountSincePause</td><td>uint32_t</td><td></td><td>loopTask (Core 1)</td><td>pinLevelChangeIsr (Core 1)</td><td>RF-Debounce nach Key-Empfang</td></tr>
<tr><td>PiepPattern::startRequest<br>PiepPattern::stopRequest</td><td>uint8_t</td><td></td><td>loopTask via Start()/Stop()</td><td>beepTask via TaskCyclic()</td><td>Cross-Core; Flags, nicht Zähler</td></tr>
<tr><td>toneRequest</td><td>uint8_t</td><td></td><td>beepTask (exklusiv)</td><td>beepTask</td><td>Kein Cross-Core-Zugriff mehr nach Refactoring</td></tr>
</table>
</section>
<!-- ═══════════════════════════════════════════════════════════
11. XCP
═══════════════════════════════════════════════════════════ -->
<section>
<h2>11 · XCP-Protokoll</h2>
<table>
<tr><th>Event</th><th>Intervall</th><th>Aufruf</th></tr>
<tr><td>XcpEvent(0)</td><td>10 ms</td><td>xcpLoop() in loopTask (millis-basiert)</td></tr>
<tr><td>XcpEvent(1)</td><td>100 ms</td><td>xcpLoop()</td></tr>
<tr><td>XcpEvent(2)</td><td>500 ms</td><td>xcpLoop()</td></tr>
<tr><td>XcpEvent(3)</td><td>1000 ms</td><td>xcpLoop()</td></tr>
</table>
<div style="color:var(--muted);font-size:11px;margin-top:6px;">
XcpPoll() + XcpSendLoop() werden ebenfalls in xcpLoop() aufgerufen.
Timing ist millis()-basiert → toleriert den 5-ms-vTaskDelay des loopTask.
</div>
</section>
</body>
</html>