Note de ce sujet :
  • Moyenne : 0 (0 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
KNX Stack - C++ Code ESP32 pour l'IDE Arduino
#1
Bonjour à tous,

Je souhaite mettre à votre disposition ce code qui permet de communiquer avec KNX sur un ESP32 sans aucune bibliothèque supplémentaire. Il peut aussi bien envoyer que recevoir les télégrammes les plus courants.

Code :
#include <WiFi.h>
#include <WiFiUdp.h>
#include <WebServer.h>
#include <time.h>


const char* projectName  = "KNX Stack";

const char* ssid         = "WLAN SSID";
const char* password     = "WLAN Passwort";

const char* ntpServer    = "pool.ntp.org";
const char* tzInfo       = "CET-1CEST,M3.5.0,M10.5.0/3";

const char* knxGatewayIp = "IP KNX Gateway";
const uint16_t knxPort   = 3671;
const uint16_t localPort = 3671;

WiFiUDP udp;
WebServer server(80);

uint8_t knxChannelId     = 0;
uint8_t txSeqNum         = 0;
bool isKnxConnected      = false;

unsigned long lastHeartbeat = 0;
const unsigned long HEARTBEAT_INTERVAL = 60000;
const unsigned long ACK_TIMEOUT        = 1000;

#define KNX_LOG_SIZE 10
struct KnxLogEntry {
  char timeStr[24];
  uint16_t ga;
  uint8_t data[16];
  uint8_t len;
};

KnxLogEntry knxLog[KNX_LOG_SIZE];
int knxLogNext = 0;
int knxLogFilled = 0;

void hexDump(const char* label, const uint8_t* buf, int len) {
  Serial.printf("%s [%d Bytes]: ", label, len);
  for (int i = 0; i < len; i++) {
    Serial.printf("%02X ", buf[i]);
  }
  Serial.println();
}

const char* knxStatusToString(uint8_t status) {
  switch (status) {
    case 0x00: return "E_NO_ERROR";
    case 0x01: return "E_HOST_PROTOCOL_TYPE";
    case 0x02: return "E_VERSION_NOT_SUPPORTED";
    case 0x04: return "E_SEQUENCE_NUMBER (Sequenznummer falsch/ausser Sync)";
    case 0x21: return "E_CONNECTION_ID (unbekannte Channel ID)";
    case 0x22: return "E_CONNECTION_TYPE";
    case 0x23: return "E_CONNECTION_OPTION";
    case 0x24: return "E_NO_MORE_CONNECTIONS (Gateway voll)";
    case 0x26: return "E_DATA_CONNECTION";
    case 0x27: return "E_KNX_CONNECTION";
    case 0x29: return "E_TUNNELING_LAYER (nicht unterstuetzt)";
    default:   return "Unbekannter Statuscode";
  }
}

String getFormattedDateTime() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo, 100)) {
    return "Zeit nicht synchronisiert";
  }
  char buf[32];
  strftime(buf, sizeof(buf), "%d.%m.%Y %H:%M:%S", &timeinfo);
  return String(buf);
}

uint16_t parseGroupAddress(const String& gaStr) {
  int firstSlash  = gaStr.indexOf('/');
  int secondSlash = gaStr.indexOf('/', firstSlash + 1);
  if (firstSlash == -1 || secondSlash == -1) return 0;

  uint8_t main   = gaStr.substring(0, firstSlash).toInt();
  uint8_t middle = gaStr.substring(firstSlash + 1, secondSlash).toInt();
  uint8_t sub    = gaStr.substring(secondSlash + 1).toInt();

  return ((main & 0x1F) << 11) | ((middle & 0x07) << 8) | (sub & 0xFF);
}

String gaToString(uint16_t ga) {
  uint8_t main   = (ga >> 11) & 0x1F;
  uint8_t middle = (ga >> 8) & 0x07;
  uint8_t sub    = ga & 0xFF;
  return String(main) + "/" + String(middle) + "/" + String(sub);
}

uint16_t floatToDpt9(float val) {
  int sign = (val < 0) ? 1 : 0;
  float v = (sign == 1) ? -val : val;
  int exp = 0;
  int mantissa = (int)(v * 100.0f);
  while (mantissa > 2047) {
    mantissa >>= 1;
    exp++;
  }
  if (sign == 1) {
    mantissa = -mantissa;
    mantissa &= 0x07FF;
  }
  return (sign << 15) | ((exp & 0x0F) << 11) | (mantissa & 0x07FF);
}

float dpt9ToFloat(uint8_t hi, uint8_t lo) {
  uint16_t raw = ((uint16_t)hi << 8) | lo;
  int sign = (raw >> 15) & 0x01;
  int exp  = (raw >> 11) & 0x0F;
  int mantissa11 = raw & 0x07FF;
  int M = mantissa11 - (sign ? 2048 : 0);
  return 0.01f * M * (float)(1 << exp);
}

void floatToDpt14Bytes(float val, uint8_t* out4) {
  uint8_t* p = (uint8_t*)&val;
  out4[0] = p[3];
  out4[1] = p[2];
  out4[2] = p[1];
  out4[3] = p[0];
}

float dpt14ToFloat(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
  uint8_t bytes[4] = { b3, b2, b1, b0 };
  float f;
  memcpy(&f, bytes, 4);
  return f;
}

String apciTypeStr(uint8_t apciByte) {
  switch (apciByte & 0xC0) {
    case 0x00: return "Read";
    case 0x40: return "Response";
    case 0x80: return "Write";
    default:   return "?";
  }
}

String interpretKnxData(uint8_t* data, uint8_t len) {
  String prefix = "[" + apciTypeStr(data[0]) + "] ";

  if (len == 1) {
    bool bitVal = data[0] & 0x01;
    return prefix + "DPT1: " + (bitVal ? "EIN (1)" : "AUS (0)");
  }
  else if (len == 2) {
    return prefix + "DPT5: " + String(data[1]) + " (0..255)";
  }
  else if (len == 3) {
    float f = dpt9ToFloat(data[1], data[2]);
    return prefix + "DPT9: " + String(f, 2);
  }
  else if (len == 4) {
    uint8_t h = data[1] & 0x1F, mnt = data[2] & 0x3F, s = data[3] & 0x3F;
    uint8_t day = data[1] & 0x1F, month = data[2] & 0x0F, year = data[3] & 0x7F;
    char buf[80];
    snprintf(buf, sizeof(buf), "DPT10 Zeit : %02d:%02d:%02d  /  DPT11 Datum : %02d.%02d.20%02d",
             h, mnt, s, day, month, year);
    return prefix + String(buf);
  }
  else if (len == 5) {
    float f = dpt14ToFloat(data[1], data[2], data[3], data[4]);
    return prefix + "DPT14: " + String(f, 3);
  }
  else if (len == 15) {
    char txt[15];
    for (int i = 0; i < 14; i++) {
      uint8_t c = data[1 + i];
      txt[i] = (c >= 32 && c < 127) ? (char)c : (c == 0 ? 0 : '.');
    }
    txt[14] = 0;
    return prefix + "DPT16: \"" + String(txt) + "\"";
  }
  return prefix + "Unbekannt (" + String(len) + " Byte)";
}

void addKnxLogEntry(uint16_t ga, uint8_t* rxBuf, uint8_t npduLen) {
  KnxLogEntry &e = knxLog[knxLogNext];
  String t = getFormattedDateTime();
  strncpy(e.timeStr, t.c_str(), sizeof(e.timeStr) - 1);
  e.timeStr[sizeof(e.timeStr) - 1] = 0;
  e.ga = ga;
  uint8_t copyLen = npduLen;
  if (copyLen > sizeof(e.data)) copyLen = sizeof(e.data);
  memcpy(e.data, &rxBuf[20], copyLen);
  e.len = copyLen;

  knxLogNext = (knxLogNext + 1) % KNX_LOG_SIZE;
  if (knxLogFilled < KNX_LOG_SIZE) knxLogFilled++;
}

uint16_t processIncomingPacket(uint8_t* rxBuf, int len) {
  if (len < 6) return 0;
  uint16_t serviceType = (rxBuf[2] << 8) | rxBuf[3];
  hexDump("[KNX RX]", rxBuf, len);

  switch (serviceType) {
    case 0x0421:
      if (len >= 10) {
        Serial.printf("   -> TUNNELING_ACK | Channel : %d | Seq : %d | Status : 0x%02X (%s)\n",
                      rxBuf[7], rxBuf[8], rxBuf[9], knxStatusToString(rxBuf[9]));
      }
      break;

    case 0x0420:
      if (len >= 21) {
        uint8_t rxChannel = rxBuf[7];
        uint8_t rxSeqNum  = rxBuf[8];
        uint16_t ga = ((uint16_t)rxBuf[16] << 8) | rxBuf[17];
        uint8_t npduLen = rxBuf[18];
        Serial.printf("   -> TUNNELING_REQUEST (Bus Telegramm) | Channel : %d | Seq : %d | GA : %s\n",
                      rxChannel, rxSeqNum, gaToString(ga).c_str());
        Serial.println("      " + interpretKnxData(&rxBuf[20], npduLen));

        if (rxChannel == knxChannelId) {
          addKnxLogEntry(ga, rxBuf, npduLen);

          uint8_t ackBuf[10] = {
            0x06, 0x10, 0x04, 0x21, 0x00, 0x0A, 0x04, knxChannelId, rxSeqNum, 0x00
          };
          udp.beginPacket(knxGatewayIp, knxPort);
          udp.write(ackBuf, sizeof(ackBuf));
          udp.endPacket();
          hexDump("[KNX TX] ACK an Bus Telegramm", ackBuf, sizeof(ackBuf));
        }
      }
      break;

    case 0x0208:
      if (len >= 8) {
        Serial.printf("   -> CONNECTIONSTATE_RESPONSE | Status : 0x%02X (%s)\n", rxBuf[7], knxStatusToString(rxBuf[7]));
        if (rxBuf[7] != 0x00) {
          Serial.println("   !!! Heartbeat fehlgeschlagen -> Tunnel als getrennt markiert !!!");
          isKnxConnected = false;
        }
      }
      break;

    case 0x0209:
      if (len >= 8) {
        uint8_t rxChannel = rxBuf[6];
        Serial.printf("   -> DISCONNECT_REQUEST vom Gateway | Channel: %d\n", rxChannel);
        if (rxChannel == knxChannelId) {
          uint8_t discResp[8] = { 0x06, 0x10, 0x02, 0x0A, 0x00, 0x08, knxChannelId, 0x00 };
          udp.beginPacket(knxGatewayIp, knxPort);
          udp.write(discResp, sizeof(discResp));
          udp.endPacket();
          hexDump("[KNX TX] DISCONNECT_RESPONSE", discResp, sizeof(discResp));
          isKnxConnected = false;
        }
      }
      break;

    default:
      Serial.printf("   -> Unbehandelter ServiceType: 0x%04X\n", serviceType);
      break;
  }
  return serviceType;
}

bool connectKnx() {
  Serial.println("[KNX] Sende CONNECT_REQUEST an Gateway...");

  IPAddress myIp = WiFi.localIP();
  Serial.printf("   Lokale IP/Port die wir dem Gateway melden : %s:%d\n", myIp.toString().c_str(), localPort);
  Serial.printf("   Ziel Gateway: %s:%d\n", knxGatewayIp, knxPort);

  uint8_t connReq[] = {
    0x06, 0x10, 0x02, 0x05, 0x00, 0x1A,
    0x08, 0x01, myIp[0], myIp[1], myIp[2], myIp[3], (uint8_t)(localPort >> 8), (uint8_t)(localPort & 0xFF),
    0x08, 0x01, myIp[0], myIp[1], myIp[2], myIp[3], (uint8_t)(localPort >> 8), (uint8_t)(localPort & 0xFF),
    0x04, 0x04, 0x02, 0x00
  };
  hexDump("[KNX TX] CONNECT_REQUEST", connReq, sizeof(connReq));

  udp.beginPacket(knxGatewayIp, knxPort);
  udp.write(connReq, sizeof(connReq));
  udp.endPacket();

  unsigned long start = millis();
  while (millis() - start < 2000) {
    int size = udp.parsePacket();
    if (size >= 8) {
      uint8_t resp[32];
      int rlen = udp.read(resp, sizeof(resp));
      hexDump("[KNX RX] CONNECT_RESPONSE", resp, rlen);

      if (resp[2] == 0x02 && resp[3] == 0x06) {
        if (resp[7] == 0x00) {
          knxChannelId = resp[6];
          txSeqNum = 0;
          isKnxConnected = true;
          lastHeartbeat = millis();
          Serial.printf("ERFOLGREICH! Channel ID : %d\n", knxChannelId);

          if (rlen >= 16 && resp[8] == 0x08) {
            IPAddress dataIp(resp[10], resp[11], resp[12], resp[13]);
            uint16_t dataPort = (resp[14] << 8) | resp[15];
            Serial.printf("   [Info] Gateway Data-Endpoint laut CONNECT_RESPONSE: %s:%d\n",
                          dataIp.toString().c_str(), dataPort);
          }
          return true;
        } else {
          Serial.printf("FEHLER! Status Code: 0x%02X (%s)\n", resp[7], knxStatusToString(resp[7]));
          return false;
        }
      }
    }
    delay(10);
  }
  Serial.println("TIMEOUT! Keine CONNECT_RESPONSE vom Gateway erhalten.");
  isKnxConnected = false;
  return false;
}

void sendConnectionStateRequest() {
  IPAddress myIp = WiFi.localIP();
  uint8_t req[16] = {
    0x06, 0x10, 0x02, 0x07, 0x00, 0x10,
    knxChannelId, 0x00,
    0x08, 0x01, myIp[0], myIp[1], myIp[2], myIp[3], (uint8_t)(localPort >> 8), (uint8_t)(localPort & 0xFF)
  };
  hexDump("[KNX TX] CONNECTIONSTATE_REQUEST (Heartbeat)", req, sizeof(req));
  udp.beginPacket(knxGatewayIp, knxPort);
  udp.write(req, sizeof(req));
  udp.endPacket();
}

void disconnectKnx() {
  if (!isKnxConnected) return;
  IPAddress myIp = WiFi.localIP();
  uint8_t req[16] = {
    0x06, 0x10, 0x02, 0x09, 0x00, 0x10,
    knxChannelId, 0x00,
    0x08, 0x01, myIp[0], myIp[1], myIp[2], myIp[3], (uint8_t)(localPort >> 8), (uint8_t)(localPort & 0xFF)
  };
  hexDump("[KNX TX] DISCONNECT_REQUEST", req, sizeof(req));
  udp.beginPacket(knxGatewayIp, knxPort);
  udp.write(req, sizeof(req));
  udp.endPacket();
  isKnxConnected = false;
}

void handleKnxRx() {
  int packetSize = udp.parsePacket();
  if (packetSize <= 0) return;
  uint8_t rxBuf[64];
  int len = udp.read(rxBuf, sizeof(rxBuf));
  processIncomingPacket(rxBuf, len);
}

bool sendKnxValue(String groupAddress, String dptType, String rawValue) {
  if (!isKnxConnected) {
    if (!connectKnx()) return false;
  }

  uint16_t ga = parseGroupAddress(groupAddress);
  uint8_t buf[64];
  uint8_t payloadLen = 0;

  buf[0] = 0x06; buf[1] = 0x10;
  buf[2] = 0x04; buf[3] = 0x20;
  buf[4] = 0x00; buf[5] = 0x00;

  buf[6] = 0x04;
  buf[7] = knxChannelId;
  buf[8] = txSeqNum;
  buf[9] = 0x00;

  buf[10] = 0x11;
  buf[11] = 0x00;
  buf[12] = 0xBC;
  buf[13] = 0xE0;
  buf[14] = 0x00; buf[15] = 0x00;
  buf[16] = (uint8_t)(ga >> 8);
  buf[17] = (uint8_t)(ga & 0xFF);

  if (dptType == "DPT1") {
    buf[18] = 0x01;
    buf[19] = 0x00;
    buf[20] = (rawValue.toInt() > 0) ? 0x81 : 0x80;
    payloadLen = 21;
  }
  else if (dptType == "DPT5") {
    buf[18] = 0x02;
    buf[19] = 0x00;
    buf[20] = 0x80;
    buf[21] = (uint8_t)rawValue.toInt();
    payloadLen = 22;
  }
  else if (dptType == "DPT9") {
    uint16_t dpt9Val = floatToDpt9(rawValue.toFloat());
    buf[18] = 0x03;
    buf[19] = 0x00;
    buf[20] = 0x80;
    buf[21] = (uint8_t)(dpt9Val >> 8);
    buf[22] = (uint8_t)(dpt9Val & 0xFF);
    payloadLen = 23;
  }
  else if (dptType == "DPT10") {
    int h = 0, m = 0, s = 0;
    int c1 = rawValue.indexOf(':');
    if (c1 == -1) {
      h = rawValue.toInt();
    } else {
      h = rawValue.substring(0, c1).toInt();
      int c2 = rawValue.indexOf(':', c1 + 1);
      if (c2 == -1) {
        m = rawValue.substring(c1 + 1).toInt();
      } else {
        m = rawValue.substring(c1 + 1, c2).toInt();
        s = rawValue.substring(c2 + 1).toInt();
      }
    }
    buf[18] = 0x04;
    buf[19] = 0x00;
    buf[20] = 0x80;
    buf[21] = (uint8_t)(h & 0x1F);
    buf[22] = (uint8_t)(m & 0x3F);
    buf[23] = (uint8_t)(s & 0x3F);
    payloadLen = 24;
  }
  else if (dptType == "DPT11") {
    int day = 1, month = 1, year = 26;
    int p1 = rawValue.indexOf('.');
    if (p1 != -1) {
      day = rawValue.substring(0, p1).toInt();
      int p2 = rawValue.indexOf('.', p1 + 1);
      if (p2 != -1) {
        month = rawValue.substring(p1 + 1, p2).toInt();
        int y = rawValue.substring(p2 + 1).toInt();
        year = (y >= 2000) ? (y - 2000) : y;
      }
    } else {
      day = rawValue.toInt();
    }
    buf[18] = 0x04;
    buf[19] = 0x00;
    buf[20] = 0x80;
    buf[21] = (uint8_t)(day & 0x1F);
    buf[22] = (uint8_t)(month & 0x0F);
    buf[23] = (uint8_t)(year & 0x7F);
    payloadLen = 24;
  }
  else if (dptType == "DPT14") {
    uint8_t db[4];
    floatToDpt14Bytes(rawValue.toFloat(), db);
    buf[18] = 0x05;
    buf[19] = 0x00;
    buf[20] = 0x80;
    buf[21] = db[0];
    buf[22] = db[1];
    buf[23] = db[2];
    buf[24] = db[3];
    payloadLen = 25;
  }
  else if (dptType == "DPT16") {
    uint8_t strLen = rawValue.length();
    if (strLen > 14) strLen = 14;

    buf[18] = 15;
    buf[19] = 0x00;
    buf[20] = 0x80;
    for (int i = 0; i < 14; i++) {
      buf[21 + i] = (i < strLen) ? rawValue[i] : 0x00;
    }
    payloadLen = 21 + 14;
  }
  else {
    Serial.println("[KNX TX] FEHLER - Unbekannter DPT Typ!");
    return false;
  }

  buf[5] = payloadLen;

  Serial.printf("[KNX TX] GA: %s (0x%04X) | DPT: %s | Val: %s | Seq: %d\n",
                groupAddress.c_str(), ga, dptType.c_str(), rawValue.c_str(), txSeqNum);
  hexDump("[KNX TX] TUNNELING_REQUEST", buf, payloadLen);

  uint8_t sentSeq = txSeqNum;

  udp.beginPacket(knxGatewayIp, knxPort);
  udp.write(buf, payloadLen);
  udp.endPacket();

  txSeqNum++;

  unsigned long waitStart = millis();
  bool ackOk = false;
  while (millis() - waitStart < ACK_TIMEOUT) {
    int packetSize = udp.parsePacket();
    if (packetSize > 0) {
      uint8_t rxBuf[64];
      int len = udp.read(rxBuf, sizeof(rxBuf));
      uint16_t st = processIncomingPacket(rxBuf, len);
      if (st == 0x0421 && len >= 9 && rxBuf[8] == sentSeq) {
        ackOk = (len >= 10 && rxBuf[9] == 0x00);
        break;
      }
    }
    delay(5);
  }

  if (!ackOk) {
    Serial.println("!!! KEIN passendes TUNNELING_ACK erhalten (Timeout nach 1000ms) !!!");
  }

  return ackOk;
}

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() {
  String html = "<html><head><meta charset='UTF-8'>";
  html += "<title>" + String(projectName) + "</title>";
  html += "<style>body{font-family:Arial,sans-serif;margin:20px;background:#f4f4f9;color:#333}";
  html += ".card{background:#fff;padding:20px;border-radius:8px;box-shadow:0 2px 5px rgba(0,0,0,0.1);max-width:650px;margin:auto}";
  html += "h1{text-align:center;margin-bottom:2px;}";
  html += "#dtNow{text-align:center;color:#666;margin-top:0;margin-bottom:16px;font-size:14px;}";
  html += "input,select,button{width:100%;padding:10px;margin:10px 0;box-sizing:border-box;border:1px solid #ccc;border-radius:4px;font-size:16px}";
  html += "button{background:#007bff;color:#fff;font-weight:bold;border:none;cursor:pointer}button:hover{background:#0056b3}";
  html += "#statusMsg{margin-top:10px;font-weight:bold;color:#28a745;text-align:center;}";
  html += "table{width:100%;border-collapse:collapse;font-size:13px;margin-top:10px;}";
  html += "th,td{border-bottom:1px solid #eee;padding:6px 4px;text-align:left;}";
  html += "th{background:#f0f0f5;}";
  html += "</style></head><body>";

  html += "<div class='card'>";
  html += "<h1>" + String(projectName) + "</h1>";
  html += "<p id='dtNow'>...</p>";
  html += "<p>KNX Status: <span id='knxStatus'>" + String(isKnxConnected ? "<b style='color:green;'>Verbunden (Channel " + String(knxChannelId) + ")" : "Getrennt") + "";

  html += "<label>Gruppenadresse (z.B. 1/0/0):</label>";
  html += "<input type='text' id='ga' value='1/0/0'>";

  html += "<label>Datenpunkttyp (DPT):</label>";
  html += "<select id='dpt'>";
  html += "<option value='DPT1'>DPT 1 (1 Bit Schalten 0/1)</option>";
  html += "<option value='DPT5'>DPT 5 (1 Byte Wert 0..255)</option>";
  html += "<option value='DPT9'>DPT 9 (2 Byte Float Temp/Wert)</option>";
  html += "<option value='DPT10'>DPT 10 (Zeit, z.B. 14:30:00)</option>";
  html += "<option value='DPT11'>DPT 11 (Datum, z.B. 24.12.26)</option>";
  html += "<option value='DPT14'>DPT 14 (4 Byte Float IEEE754)</option>";
  html += "<option value='DPT16'>DPT 16 (14 Byte Text)</option>";
  html += "</select>";

  html += "<label>Wert / Payload:</label>";
  html += "<input type='text' id='val' value='1' placeholder='z.B. 1, 128, 21.5, 14:30:00, 24.12.26 oder Text'>";

  html += "<button type='button' onclick='sendTelegram()'>KNX Telegramm Senden</button>";
  html += "<button type='button' onclick='reconnectKnx()' style='background:#6c757d;'>KNX Reconnect</button>";
  html += "<div id='statusMsg'></div>";

  html += "<h3>Letzte empfangene Telegramme</h3>";
  html += "<table><thead><tr><th>Zeit</th><th>GA</th><th>Bytes</th><th>Hex</th><th>Wert</th></tr></thead>";
  html += "<tbody id='logBody'>Lade...</td></tr></tbody></table>";

  html += "</div>";

  html += "<script>";
  html += "function sendTelegram(){";
  html += "  var ga = document.getElementById('ga').value;";
  html += "  var dpt = document.getElementById('dpt').value;";
  html += "  var val = document.getElementById('val').value;";
  html += "  document.getElementById('statusMsg').innerText = 'Sende...';";
  html += "  fetch('/send?ga=' + encodeURIComponent(ga) + '&dpt=' + encodeURIComponent(dpt) + '&val=' + encodeURIComponent(val))";
  html += "  .then(response => response.text())";
  html += "  .then(data => { document.getElementById('statusMsg').innerText = data; })";
  html += "  .catch(err => { document.getElementById('statusMsg').innerText = 'Fehler beim Senden!'; });";
  html += "}";
  html += "function reconnectKnx(){";
  html += "  fetch('/reconnect').then(r=>r.text()).then(d=>{ updateStatus(); });";
  html += "}";
  html += "function updateStatus(){";
  html += "  fetch('/status').then(r=>r.json()).then(d=>{";
  html += "    document.getElementById('dtNow').innerText = d.datetime;";
  html += "    document.getElementById('knxStatus').innerHTML = d.connected ? (\"<b style='color:green;'>Verbunden (Channel \" + d.channel + \")</b>\") : \"<b style='color:red;'>Getrennt</b>\";";
  html += "    var rows = '';";
  html += "    d.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>'; });";
  html += "    document.getElementById('logBody').innerHTML = rows || '<tr><td colspan=5>Noch keine Telegramme empfangen</td></tr>';";
  html += "  }).catch(function(){});";
  html += "}";
  html += "setInterval(updateStatus, 1000);";
  html += "updateStatus();";
  html += "</script>";

  html += "</body></html>";
  server.send(200, "text/html", html);
}

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 += "\"connected\":" + String(isKnxConnected ? "true" : "false") + ",";
  json += "\"channel\":" + String(knxChannelId) + ",";
  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 setup() {
  Serial.begin(115200);
  delay(1000);

  Serial.printf("\n===> %s <===\n", projectName);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWLAN Verbunden!");
  Serial.print("IP Adresse - "); Serial.println(WiFi.localIP());
  Serial.print("RSSI: "); Serial.println(WiFi.RSSI());

  configTzTime(tzInfo, ntpServer);
  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).");
  }

  udp.begin(localPort);

  connectKnx();

  server.on("/", handleRoot);
  server.on("/send", handleSend);
  server.on("/reconnect", handleReconnect);
  server.on("/status", handleStatus);
  server.begin();
}

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

  if (isKnxConnected && (millis() - lastHeartbeat > HEARTBEAT_INTERVAL)) {
    sendConnectionStateRequest();
    lastHeartbeat = millis();
  }
}
Répondre
#2
je teste des que j'ai un peu de temps
Répondre


Atteindre :


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