Note de ce sujet :
  • Moyenne : 0 (0 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
KNX Stack - C++ Code ESP32 pour l'IDE Arduino
#5
Voici la deuxième partie du code. 

Code :
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>KNX - LED Effect</title>
  <style>
    :root {
      --bg: #0b1120;
      --card: #151e2f;
      --border: #334155;
      --text: #f8fafc;
      --muted: #94a3b8;
      --red: #ef4444;
      --green: #22c55e;
      --blue: #3b82f6;
      --primary: #6366f1;
      --primary2: #818cf8;
      --rainbow: linear-gradient(135deg, #ef4444, #f59e0b, #10b981, #3b82f6, #8b5cf6);
    }
    * { box-sizing: border-box; }
    body {
      margin: 0; min-height: 100vh;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: radial-gradient(circle at top, #1e1b4b 0%, var(--bg) 50%);
      color: var(--text);
    }
    .container { width: 100%; max-width: 600px; margin: auto; padding: 20px 16px 40px; }
    .header { text-align: center; margin-bottom: 20px; }
    .header h1 { margin: 0; font-size: 26px; }
    .subtitle { color: var(--muted); font-size: 14px; margin-top: 4px; }

    .card {
      background: rgba(21, 30, 47, 0.9); border: 1px solid var(--border);
      border-radius: 18px; padding: 20px; margin-bottom: 16px;
      backdrop-filter: blur(10px); box-shadow: 0 10px 30px rgba(0,0,0,0.3);
    }
    .card-title { font-size: 16px; font-weight: 700; margin-bottom: 15px; color: var(--primary2); display: flex; justify-content: space-between; align-items: center;}

    .preview-box {
      width: 100%; height: 50px; border-radius: 12px; margin-bottom: 20px;
      border: 2px solid var(--border); transition: background 0.1s;
      box-shadow: inset 0 2px 10px rgba(0,0,0,0.5);
    }

    .topbar { display:flex; flex-wrap:wrap; gap:8px; justify-content:center; margin-bottom:16px; }
    .pill { background: var(--card); border:1px solid var(--border); border-radius:20px; padding:6px 14px; font-size:12.5px; color:var(--muted); }
    b.ok{color:#22c55e;} b.err{color:#ef4444;}

    .led-toggle { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; }
    .onoff-btn { border:none; border-radius:24px; padding:10px 22px; font-weight:700; font-size:13px; cursor:pointer; background:#1e293b; color:var(--muted); }
    .onoff-btn.on { background:linear-gradient(90deg,#3b82f6,#22c55e); color:#0b1120; }

    .slider-group { margin-bottom: 14px; }
    .slider-header { display: flex; justify-content: space-between; font-size: 14px; font-weight: 600; margin-bottom: 6px; }

    .slider {
      width: 100%; height: 8px; border-radius: 10px; appearance: none; outline: none;
      background: #1e293b; cursor: pointer;
    }
    .slider::-webkit-slider-thumb {
      appearance: none; width: 22px; height: 22px; border-radius: 50%;
      background: #fff; border: 3px solid var(--border); cursor: pointer;
      box-shadow: 0 2px 8px rgba(0,0,0,0.4);
    }

    .slider-master::-webkit-slider-thumb { border-color: var(--primary); }
    .slider-speed::-webkit-slider-thumb { border-color: #f59e0b; }
    .slider-r::-webkit-slider-thumb { border-color: var(--red); }
    .slider-g::-webkit-slider-thumb { border-color: var(--green); }
    .slider-b::-webkit-slider-thumb { border-color: var(--blue); }

    .effects-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 10px; }
    @media (min-width: 480px) { .effects-grid { grid-template-columns: repeat(4, 1fr); } }

    .btn-fx {
      background: #1e293b; border: 1px solid var(--border); color: var(--text);
      padding: 12px 8px; border-radius: 10px; font-size: 12px; font-weight: 600;
      cursor: pointer; text-align: center; transition: .2s;
    }
    .btn-fx:hover { border-color: var(--primary); transform: translateY(-1px); }
    .btn-fx.active { background: var(--primary); border-color: var(--primary); box-shadow: 0 4px 15px rgba(99,102,241,0.4); }

    .btn-rainbow { border: 1px solid rgba(255,255,255,0.2); }
    .btn-rainbow:hover { border-color: #ec4899; }
    .btn-rainbow.active { background: var(--rainbow); border-color: #ffffff; box-shadow: 0 4px 15px rgba(236,72,153,0.5); }

    .btn-stop { background: #ef4444; border: 1px solid #dc2626; color: white; padding: 8px 16px; border-radius: 8px; font-weight: bold; cursor: pointer; font-size: 12px; }

    .picker-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 15px; background: #0f172a; padding: 10px 14px; border-radius: 10px; }
    .color-picker { width: 60px; height: 35px; border: none; border-radius: 8px; cursor: pointer; background: none; }

    .info-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid rgba(148,163,184,0.1); font-size: 13px; }
    .info-row:last-child { border-bottom: none; }
    .info-label { color: var(--muted); }

    details { background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 16px 20px; margin-top: 4px; }
    summary { cursor: pointer; font-weight: 700; font-size: 13.5px; color: var(--muted); }
    input[type=text], select, button.full { width:100%; padding:10px; margin:8px 0; box-sizing:border-box; border:1px solid var(--border); border-radius:8px; font-size:14px; background:#0f172a; color:var(--text); font-family:inherit; }
    button.full { background:#2d6fb3; color:#fff; font-weight:700; cursor:pointer; border:none; }
    #statusMsg { min-height: 18px; color: #22c55e; font-size: 13px; }
    table { width:100%; border-collapse:collapse; font-size:12.5px; margin-top:8px; }
    th, td { border-bottom:1px solid var(--border); padding:6px 4px; text-align:left; color:var(--muted); }
    th { color: var(--text); }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <h1>KNX - RGB Effect</h1>
      <div class="subtitle">ESP32 Dynamic Lighting + KNX</div>
    </div>

    <div class="topbar">
      <div class="pill" id="dtNow">...</div>
      <div class="pill">KNX: <span id="knxStatus">...</span></div>
    </div>

    <div class="card">
      <div id="preview" class="preview-box"></div>

      <div class="led-toggle">
        <span>Controller Ein/Aus</span>
        <button class="onoff-btn" id="onoffBtn" onclick="toggleOnOff()">...</button>
      </div>

      <div class="slider-group">
        <div class="slider-header">
          <span>Gesamthelligkeit (Master)</span>
          <span id="valMaster">100%</span>
        </div>
        <input type="range" id="sliderMaster" class="slider slider-master" min="0" max="100" value="100">
      </div>

      <div class="slider-group">
        <div class="slider-header" style="color: #f59e0b;">
          <span>Effekt-Geschwindigkeit (Speed)</span>
          <span id="valSpeed">50%</span>
        </div>
        <input type="range" id="sliderSpeed" class="slider slider-speed" min="1" max="100" value="50">
      </div>
    </div>

    <div class="card">
      <div class="card-title">
        <span>16 Programme (Mit Wunschfarbe)</span>
        <button class="btn-stop" onclick="stopEffect()">⏹ Effekte Aus</button>
      </div>

      <div class="picker-row">
        <span style="font-size: 13px; color: #f8fafc; font-weight: 600;">Farbe für Programme wählen:</span>
        <input type="color" id="fxPicker" class="color-picker" value="#ff0000">
      </div>

      <div class="effects-grid">
        <button class="btn-fx" id="fx1" onclick="setEffect(1)">1. Fade Pulse</button>
        <button class="btn-fx" id="fx2" onclick="setEffect(2)">2. Fast Pulse</button>
        <button class="btn-fx" id="fx3" onclick="setEffect(3)">3. Knight Rider</button>
        <button class="btn-fx" id="fx4" onclick="setEffect(4)">4. Stroboskop</button>
        <button class="btn-fx" id="fx5" onclick="setEffect(5)">5. Blitz-Strobo</button>
        <button class="btn-fx" id="fx6" onclick="setEffect(6)">6. Atmung</button>
        <button class="btn-fx" id="fx7" onclick="setEffect(7)">7. Kerzenflackern</button>
        <button class="btn-fx" id="fx8" onclick="setEffect(8)">8. Martinshorn</button>
        <button class="btn-fx" id="fx9" onclick="setEffect(9)">9. Gewitter-Blitze</button>
        <button class="btn-fx" id="fx10" onclick="setEffect(10)">10. Feuerflackern</button>
        <button class="btn-fx" id="fx11" onclick="setEffect(11)">11. Random Takt</button>
        <button class="btn-fx" id="fx12" onclick="setEffect(12)">12. Smooth Waves</button>
        <button class="btn-fx" id="fx13" onclick="setEffect(13)">13. Dimmer Cycle</button>
        <button class="btn-fx" id="fx14" onclick="setEffect(14)">14. Sparkle / Funken</button>
        <button class="btn-fx" id="fx15" onclick="setEffect(15)">15. Dual Flash</button>
        <button class="btn-fx" id="fx16" onclick="setEffect(16)">16. Herzschlag</button>
      </div>
    </div>

    <div class="card" style="border-color: #818cf8;">
      <div class="card-title" style="color: #ec4899;">
        <span>? 8 Regenbogen & Multi-Color</span>
      </div>

      <div class="effects-grid">
        <button class="btn-fx btn-rainbow" id="fx17" onclick="setEffect(17)">17. Rainbow Loop</button>
        <button class="btn-fx btn-rainbow" id="fx18" onclick="setEffect(18)">18. Rainbow Chase</button>
        <button class="btn-fx btn-rainbow" id="fx19" onclick="setEffect(19)">19. Party Strobo</button>
        <button class="btn-fx btn-rainbow" id="fx20" onclick="setEffect(20)">20. Disko Random</button>
        <button class="btn-fx btn-rainbow" id="fx21" onclick="setEffect(21)">21. Aurora Ozean</button>
        <button class="btn-fx btn-rainbow" id="fx22" onclick="setEffect(22)">22. Sunset Fire</button>
        <button class="btn-fx btn-rainbow" id="fx23" onclick="setEffect(23)">23. Neon Party</button>
        <button class="btn-fx btn-rainbow" id="fx24" onclick="setEffect(24)">24. Polizei Blau/Rot</button>
      </div>
    </div>

    <div class="card">
      <div class="card-title">Manuelle Farbwahl</div>

      <div class="slider-group">
        <div class="slider-header" style="color: var(--red);"><span>Rot</span><span id="valR">255</span></div>
        <input type="range" id="sliderR" class="slider slider-r" min="0" max="255" value="255">
      </div>
      <div class="slider-group">
        <div class="slider-header" style="color: var(--green);"><span>Grün</span><span id="valG">255</span></div>
        <input type="range" id="sliderG" class="slider slider-g" min="0" max="255" value="255">
      </div>
      <div class="slider-group">
        <div class="slider-header" style="color: var(--blue);"><span>Blau</span><span id="valB">255</span></div>
        <input type="range" id="sliderB" class="slider slider-b" min="0" max="255" value="255">
      </div>
    </div>

    <div class="card">
      <div class="info-row">
        <span class="info-label">Datum & Uhrzeit</span>
        <span id="infoDatetime" style="font-weight: 600; color: var(--primary2);">Wird geladen...</span>
      </div>
      <div class="info-row"><span class="info-label">Status</span><span id="infoStatus">Bereit</span></div>
    </div>

    <details>
      <summary>Erweitert - Manueller KNX Befehl & Telegrammverlauf</summary>
      <label>Gruppenadresse</label><input type="text" id="ga" value="1/0/0">
      <label>Datenpunkttyp (DPT)</label><select id="dpt">
        <option value="DPT1">DPT 1 (1 Bit)</option>
        <option value="DPT2">DPT 2 (2 Bit, C+V, 0..3)</option>
        <option value="DPT3">DPT 3 (Dimmen, -7..7)</option>
        <option value="DPT5">DPT 5 (1 Byte 0..255)</option>
        <option value="DPT9">DPT 9 (2 Byte Float)</option>
        <option value="DPT10">DPT 10 (Zeit, 14:30:00)</option>
        <option value="DPT11">DPT 11 (Datum, 24.12.26)</option>
        <option value="DPT14">DPT 14 (4 Byte Float)</option>
        <option value="DPT16">DPT 16 (Text)</option>
        <option value="DPT232">DPT 232 (RGB, z.B. 255,128,0)</option>
      </select>
      <label>Wert / Payload</label><input type="text" id="val" value="1">
      <button class="full" onclick="sendTelegram()">KNX Telegramm Senden</button>
      <button class="full" style="background:#3a3f4d;" onclick="fetch('/reconnect').then(()=>syncStatus())">KNX Reconnect</button>
      <div id="statusMsg"></div>
      <table><thead><tr><th>Zeit</th><th>GA</th><th>Bytes</th><th>Hex</th><th>Wert</th></tr></thead>
      <tbody id="logBody"><tr><td colspan="5">Lade...</td></tr></tbody></table>
    </details>
  </div>

  <script>
    const sM = document.getElementById("sliderMaster");
    const sSp = document.getElementById("sliderSpeed");
    const sR = document.getElementById("sliderR");
    const sG = document.getElementById("sliderG");
    const sB = document.getElementById("sliderB");
    const fxPicker = document.getElementById("fxPicker");
    const preview = document.getElementById("preview");
    const infoDatetime = document.getElementById("infoDatetime");
    const onoffBtn = document.getElementById("onoffBtn");

    let sendTimer;
    let currentServerTime = null;
    let isUserInteracting = false;
    let currentActiveFx = 0;
    let ledIsOn = true;

    [sM, sSp, sR, sG, sB].forEach(el => {
      el.addEventListener("mousedown", () => isUserInteracting = true);
      el.addEventListener("touchstart", () => isUserInteracting = true);
      el.addEventListener("mouseup", () => isUserInteracting = false);
      el.addEventListener("touchend", () => isUserInteracting = false);
    });

    sM.addEventListener("input", onSliderChange);
    sR.addEventListener("input", onRGBColorSliderChange);
    sG.addEventListener("input", onRGBColorSliderChange);
    sB.addEventListener("input", onRGBColorSliderChange);
    sSp.addEventListener("input", onSpeedSliderChange);

    fxPicker.addEventListener("input", function() {
      const hex = this.value;
      const r = parseInt(hex.substr(1,2), 16);
      const g = parseInt(hex.substr(3,2), 16);
      const b = parseInt(hex.substr(5,2), 16);

      sR.value = r; sG.value = g; sB.value = b;
      updateUI();

      fetch(`/api/fxcolor?r=${r}&g=${g}&b=${b}`).catch(e => console.log(e));
    });

    function onRGBColorSliderChange() {
      if (currentActiveFx !== 0) {
        stopEffect();
      }
      onSliderChange();
    }

    function onSpeedSliderChange() {
      updateUI();
      clearTimeout(sendTimer);
      sendTimer = setTimeout(() => {
        fetch(`/api/speed?val=${sSp.value}`).catch(e => console.log(e));
      }, 40);
    }

    function onSliderChange() {
      updateUI();
      clearTimeout(sendTimer);
      sendTimer = setTimeout(sendData, 40);
    }

    function toggleOnOff() {
      ledIsOn = !ledIsOn;
      applyOnOffUI();
      fetch(`/api/onoff?val=${ledIsOn ? 1 : 0}`).catch(e => console.log(e));
    }

    function applyOnOffUI() {
      onoffBtn.classList.toggle("on", ledIsOn);
      onoffBtn.innerText = ledIsOn ? "AN" : "AUS";
    }

    function setEffect(fxId) {
      currentActiveFx = fxId;
      highlightFxButton(fxId);
      fetch(`/api/effect?id=${fxId}&speed=${sSp.value}`).catch(e => console.log(e));
    }

    function stopEffect() {
      currentActiveFx = 0;
      highlightFxButton(0);
      fetch(`/api/effect?id=0&speed=${sSp.value}`).catch(e => console.log(e));
    }

    function highlightFxButton(fxId) {
      for (let i = 1; i <= 24; i++) {
        const btn = document.getElementById(`fx${i}`);
        if (btn) {
          if (i === fxId) btn.classList.add("active");
          else btn.classList.remove("active");
        }
      }
    }

    function updateUI() {
      document.getElementById("valMaster").innerText = sM.value + "%";
      document.getElementById("valSpeed").innerText = sSp.value + "%";
      document.getElementById("valR").innerText = sR.value;
      document.getElementById("valG").innerText = sG.value;
      document.getElementById("valB").innerText = sB.value;

      const factor = sM.value / 100;
      const r = Math.round(sR.value * factor);
      const g = Math.round(sG.value * factor);
      const b = Math.round(sB.value * factor);

      preview.style.background = `rgb(${r}, ${g}, ${b})`;
      preview.style.boxShadow = `0 0 20px rgb(${r}, ${g}, ${b})`;
    }

    function sendData() {
      const url = `/api/rgb?r=${sR.value}&g=${sG.value}&b=${sB.value}&m=${sM.value}&speed=${sSp.value}`;
      fetch(url).catch(e => console.log(e));
    }

    function updateClockLocally() {
      if (!currentServerTime) return;
      currentServerTime.setSeconds(currentServerTime.getSeconds() + 1);
      const d = currentServerTime;
      const pad = n => String(n).padStart(2, '0');
      infoDatetime.innerText = `${pad(d.getDate())}.${pad(d.getMonth()+1)}.${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
    }

    function flash(t) {
      const el = document.getElementById('statusMsg');
      if (el) { el.innerText = t; clearTimeout(window._st); window._st = setTimeout(() => el.innerText = '', 2500); }
    }

    function sendTelegram() {
      const ga = document.getElementById('ga').value;
      const dpt = document.getElementById('dpt').value;
      const val = document.getElementById('val').value;
      fetch('/send?ga=' + encodeURIComponent(ga) + '&dpt=' + encodeURIComponent(dpt) + '&val=' + encodeURIComponent(val))
        .then(r => r.text()).then(flash).catch(() => {});
    }

    function syncStatus() {
      fetch("/api/status")
        .then(res => res.json())
        .then(data => {
          document.getElementById('dtNow').innerText = data.datetime;
          document.getElementById('knxStatus').innerHTML = data.knxConnected
            ? "<b class='ok'>Verbunden (Ch " + data.knxChannel + ")</b>"
            : "<b class='err'>Getrennt</b>";

          ledIsOn = data.on;
          applyOnOffUI();

          if (!isUserInteracting) {
            sR.value = data.r; sG.value = data.g; sB.value = data.b;
            sM.value = data.m; sSp.value = data.speed;
          }
          currentActiveFx = data.fx;
          highlightFxButton(data.fx);
          updateUI();
          if (data.epoch > 0) currentServerTime = new Date(data.epoch * 1000);

          var rows = '';
          (data.log || []).forEach(function(e) {
            rows += '<tr><td>' + e.time + '</td><td>' + e.ga + '</td><td>' + e.len + '</td><td>' + e.hex + '</td><td>' + e.value + '</td></tr>';
          });
          document.getElementById('logBody').innerHTML = rows || '<tr><td colspan="5">Noch keine Telegramme empfangen</td></tr>';
        }).catch(e => console.log(e));
    }

    syncStatus();
    setInterval(updateClockLocally, 1000);
    setInterval(syncStatus, 1000);
  </script>
</body>
</html>
)rawliteral";

String escapeJson(String s) {
  String out;
  for (size_t i = 0; i < s.length(); i++) {
    char c = s[i];
    if (c == '"' || c == '\\') out += '\\';
    out += c;
  }
  return out;
}

void handleRoot() {
  server.send_P(200, "text/html; charset=utf-8", INDEX_HTML);
}

void handleRGB() {
  if (server.hasArg("r") && server.hasArg("g") && server.hasArg("b") && server.hasArg("m")) {
    int r = server.arg("r").toInt();
    int g = server.arg("g").toInt();
    int b = server.arg("b").toInt();
    int m = server.arg("m").toInt();

    applyEffectSet(0);
    applyMasterPercent(m);
    applyColor(r, g, b);
    if (server.hasArg("speed")) applySpeedPercent(server.arg("speed").toInt());

    server.send(200, "application/json", "{\"success\":true}");
  } else {
    server.send(400, "text/plain", "Fehlende Parameter");
  }
}

void handleSpeed() {
  if (server.hasArg("val")) {
    applySpeedPercent(server.arg("val").toInt());
    server.send(200, "application/json", "{\"success\":true}");
  } else {
    server.send(400, "text/plain", "Parameter fehlt");
  }
}

void handleEffect() {
  if (server.hasArg("id")) {
    applyEffectSet(server.arg("id").toInt());
    if (server.hasArg("speed")) applySpeedPercent(server.arg("speed").toInt());
    server.send(200, "application/json", "{\"success\":true}");
  } else {
    server.send(400, "text/plain", "Parameter fehlt");
  }
}

void handleFxColor() {
  if (server.hasArg("r") && server.hasArg("g") && server.hasArg("b")) {
    applyColor(server.arg("r").toInt(), server.arg("g").toInt(), server.arg("b").toInt());
    server.send(200, "application/json", "{\"success\":true}");
  } else {
    server.send(400, "text/plain", "Parameter fehlt");
  }
}

void handleOnOff() {
  if (server.hasArg("val")) {
    applyOnOff(server.arg("val").toInt() > 0);
    server.send(200, "application/json", "{\"success\":true}");
  } else {
    server.send(400, "text/plain", "Parameter fehlt");
  }
}

void handleSend() {
  if (server.hasArg("ga") && server.hasArg("dpt") && server.hasArg("val")) {
    String ga = server.arg("ga");
    String dpt = server.arg("dpt");
    String val = server.arg("val");

    bool ok = sendKnxValue(ga, dpt, val);
    server.send(200, "text/plain", ok ? "Telegramm gesendet und per ACK bestaetigt!" : "Gesendet, aber KEIN ACK erhalten - siehe Serial-Monitor!");
  } else {
    server.send(400, "text/plain", "Fehlende Parameter!");
  }
}

void handleReconnect() {
  connectKnx();
  server.send(200, "text/plain", "Reconnected!");
}

void handleStatus() {
  String json = "{";
  json += "\"project\":\"" + escapeJson(String(projectName)) + "\",";
  json += "\"datetime\":\"" + escapeJson(getFormattedDateTime()) + "\",";
  json += "\"knxConnected\":" + String(isKnxConnected ? "true" : "false") + ",";
  json += "\"knxChannel\":" + String(knxChannelId) + ",";
  json += "\"on\":" + String(ledOn ? "true" : "false") + ",";
  json += "\"r\":" + String(targetR);
  json += ",\"g\":" + String(targetG);
  json += ",\"b\":" + String(targetB);
  json += ",\"m\":" + String(targetMaster);
  json += ",\"speed\":" + String(effectSpeed);
  json += ",\"fx\":" + String(activeEffect);
  json += ",\"colorTemp\":" + String(colorTempPercent);
  json += ",\"hue\":" + String(hueDegrees);
  json += ",\"epoch\":" + String(getEpochTime());

  json += ",\"log\":[";
  for (int i = 0; i < knxLogFilled; i++) {
    int idx = (knxLogNext - 1 - i + KNX_LOG_SIZE) % KNX_LOG_SIZE;
    KnxLogEntry &e = knxLog[idx];

    String hex = "";
    for (int b = 0; b < e.len; b++) {
      char hb[4];
      snprintf(hb, sizeof(hb), "%02X ", e.data[b]);
      hex += hb;
    }
    hex.trim();

    if (i > 0) json += ",";
    json += "{";
    json += "\"time\":\"" + escapeJson(String(e.timeStr)) + "\",";
    json += "\"ga\":\"" + escapeJson(gaToString(e.ga)) + "\",";
    json += "\"len\":" + String(e.len) + ",";
    json += "\"hex\":\"" + escapeJson(hex) + "\",";
    json += "\"value\":\"" + escapeJson(interpretKnxData(e.data, e.len)) + "\"";
    json += "}";
  }
  json += "]}";
  server.send(200, "application/json", json);
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.setHostname(HOSTNAME);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Verbinde WLAN");
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("IP - "); Serial.println(WiFi.localIP());
  }
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.printf("\n===> %s <===\n", projectName);

  preferences.begin("rgb-dimmer", false);
  loadSettings();

  ledcAttach(PIN_RED, PWM_FREQUENCY, PWM_RESOLUTION);
  ledcAttach(PIN_GREEN, PWM_FREQUENCY, PWM_RESOLUTION);
  ledcAttach(PIN_BLUE, PWM_FREQUENCY, PWM_RESOLUTION);
  writePWMHardware(ledOn ? currentR : 0, ledOn ? currentG : 0, ledOn ? currentB : 0);

  gaLedOnOffParsed   = parseGroupAddress(gaLedOnOff);
  gaEffectStepParsed = parseGroupAddress(gaEffectStep);
  gaEffectSetParsed  = parseGroupAddress(gaEffectSet);
  gaColorRGBParsed   = parseGroupAddress(gaColorRGB);
  gaBrightnessParsed = parseGroupAddress(gaBrightness);
  gaSpeedParsed      = parseGroupAddress(gaSpeed);
  gaColorTempStepParsed = parseGroupAddress(gaColorTempStep);
  gaHueStepParsed       = parseGroupAddress(gaHueStep);

  connectWiFi();
  if (WiFi.status() == WL_CONNECTED) {
    configTzTime(TZ_INFO, NTP_SERVER);
    Serial.println("Warte auf Zeitsynchronisation (NTP)...");
    struct tm timeinfo;
    if (getLocalTime(&timeinfo, 5000)) {
      Serial.println("Zeit synchronisiert: " + getFormattedDateTime());
    } else {
      Serial.println("Zeit konnte nicht synchronisiert werden (wird spaeter automatisch nachgeholt).");
    }
    if (MDNS.begin(HOSTNAME)) MDNS.addService("http", "tcp", 80);
  }

  udp.begin(localPort);
  connectKnx(true);

  server.on("/", HTTP_GET, handleRoot);
  server.on("/api/rgb", HTTP_GET, handleRGB);
  server.on("/api/speed", HTTP_GET, handleSpeed);
  server.on("/api/effect", HTTP_GET, handleEffect);
  server.on("/api/fxcolor", HTTP_GET, handleFxColor);
  server.on("/api/onoff", HTTP_GET, handleOnOff);
  server.on("/api/status", HTTP_GET, handleStatus);
  server.on("/send", HTTP_GET, handleSend);
  server.on("/reconnect", HTTP_GET, handleReconnect);
  server.begin();

  Serial.println("Webserver gestartet. 'help' fuer Serial-Befehle.");
}

void loop() {
  server.handleClient();
  handleKnxRx();
  handleSerialCommands();

  if (!ledOn) {
    if (currentR != 0 || currentG != 0 || currentB != 0) {
      currentR = currentG = currentB = 0;
      isFading = false;
      writePWMHardware(0, 0, 0);
    }
  } else {
    processEffects();
    updateFade();
    processColorTempDim();
    processHueDim();
  }

  if (isKnxConnected) {
    if (millis() - lastHeartbeat > HEARTBEAT_INTERVAL) {
      sendConnectionStateRequest();
      lastHeartbeat = millis();
    }
  } else {
    static unsigned long lastReconnectAttempt = 0;
    if (millis() - lastReconnectAttempt > 3000) {
      connectKnx();
      lastReconnectAttempt = millis();
    }
  }
}
Répondre


Messages dans ce sujet
KNX Stack - C++ Code ESP32 pour l'IDE Arduino - par Smart Guido 73 - 16/09/2026, 21:07:41

Atteindre :


Utilisateur(s) parcourant ce sujet : 1 visiteur(s)