Files
platformio_fensterpiepser_e…/architecture.html
T

482 lines
32 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 beepSemaphore (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">· xSemaphoreGiveFromISR(beepSemaphore) + 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 ── -->
<!-- beepSemaphore 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="442" y="98" width="82" height="18" rx="3" fill="#0f1117"/>
<text x="483" y="111" text-anchor="middle" fill="#4f8ef7" font-size="10">beepSemaphore</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[32]</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">beepSemaphore</span></td>
<td>Binary Semaphore</td><td></td>
<td><span class="tag t-isr">timerIsr</span> alle 10 ms</td>
<td><span class="tag t-task">beepTask</span> Core 0, Prio 5</td>
<td>Takt für Beep-State-Machine; ersetzt direkten ISR-Aufruf von PiepPattern::TaskCyclic()</td>
</tr>
<tr>
<td><span class="tag t-task">rfEventQueue</span></td>
<td>Queue RfEvent_t</td><td>32 Einträge × 5 B</td>
<td><span class="tag t-isr">pinLevelChangeIsr</span> bei jeder Flanke</td>
<td><span class="tag t-task">rfDecodeTask</span> Core 0, Prio 4</td>
<td>Überträgt {dtMicros, pinValue}; alle 5 Receiver-State-Machines laufen jetzt im Task-Kontext</td>
</tr>
</table>
</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">beepSemaphore</text>
<text x="200" y="205" text-anchor="middle" fill="#6b7194" font-size="10">binary</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>