<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Waage RENPHO ES-26M per MQTT &amp; ESP32 anbinden]]></title><description><![CDATA[<p dir="auto">Moin zusammen,</p>
<p dir="auto">ich habe mit ChatGPT zusammen erfolgreich die Waage RENPHO ES-26M per MQTT angebunden.<br />
Notwendig ist ein ESP32 S3 Zero, bestimmt funktionieren auch andere, aber habe ich nicht getestet.<br />
Per BT-Scannner muss die MAC-Adresse der Waage herausgefunden werden.<br />
Dann die MAC der Waage, die Wlan Daten und den MQTT-Server eintragen.</p>
<p dir="auto">Einzige mir bekannte Limitierung ist, zwischen dem Wiegevorgängen müssen ca. 10 Sekunden liegen.</p>
<p dir="auto">Alles ohne Garantie!</p>
<p dir="auto">Hier der Sketch:</p>
<p dir="auto">Viel Erfolg!<br />
Leif</p>
<pre><code>/***************************************************************
 * ESP32 S3 Zero BLE -&gt; MQTT Bridge für RENPHO / QN-Scale
 * -------------------------------------------------------------
 * Name:        RENPHO ES-26M / QN-Scale BLE MQTT Bridge
 * Version:     0.4.34
 * Stand:       2026-05-23
 ***************************************************************/

#include &lt;Arduino.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;PubSubClient.h&gt;
#include &lt;NimBLEDevice.h&gt;
#include &lt;math.h&gt;
#include &lt;string.h&gt;


/***************************************************************
 * 1) KONFIGURATION
 ***************************************************************/

const bool DEBUG = true;
const bool DEBUG_VERBOSE_SCAN = false;
const bool DEBUG_VERBOSE_PACKETS = false;
const bool DEBUG_LOG_LIVE_WEIGHT_PACKETS = false;

const bool DEBUG_SESSION_TRACE = true;
const bool DEBUG_UNKNOWN_PACKETS = true;

// WLAN
const char* WIFI_SSID     = "SSID";
const char* WIFI_PASSWORD = "Passwort";

// MQTT
const char* MQTT_HOST      = "IP-Adresse";
const uint16_t MQTT_PORT   = 1883;
const char* MQTT_USER      = "";
const char* MQTT_PASSWORD  = "";
const char* MQTT_CLIENT_ID = "esp32-renpho-es26m";

// MQTT Topics
const char* MQTT_TOPIC_STATUS           = "renpho/es26m/status";
const char* MQTT_TOPIC_WEIGHT_KG        = "renpho/es26m/weight_kg";
const char* MQTT_TOPIC_FINAL            = "renpho/es26m/final";
const char* MQTT_TOPIC_RESISTANCE_1     = "renpho/es26m/resistance_1";
const char* MQTT_TOPIC_RESISTANCE_2     = "renpho/es26m/resistance_2";
const char* MQTT_TOPIC_LAST_MEASUREMENT = "renpho/es26m/last_measurement";
const char* MQTT_TOPIC_RAW_FINAL        = "renpho/es26m/raw_final";
const char* MQTT_TOPIC_RSSI             = "renpho/es26m/rssi";
const char* MQTT_TOPIC_ERROR            = "renpho/es26m/error";
const char* MQTT_TOPIC_SESSION_DEBUG    = "renpho/es26m/session_debug";
const char* MQTT_TOPIC_REARM_STATE      = "renpho/es26m/rearm_state";

const bool MQTT_RETAIN_VALUES = true;
const bool MQTT_RETAIN_STATUS = false;

// Bekannte Waage
const char* SCALE_ADDRESS = "xx:xx:xx:xx:xx:xx";

// Scan
const uint32_t FIND_SCAN_TOTAL_WINDOW_MS = 120000;
const uint32_t FIND_SCAN_CHUNK_SEC       = 2;

// BLE Ablauf
const uint32_t RECONNECT_PAUSE_MS        = 10;
const uint32_t CONNECTED_LOOP_DELAY_MS   = 50;

const uint32_t AFTER_CONNECT_DELAY_MS    = 700;
const uint32_t AFTER_SERVICE_DELAY_MS    = 300;
const uint32_t AFTER_FFE1_SUBSCRIBE_MS   = 120;
const uint32_t AFTER_FFE1_STABILIZE_MS   = 1500;
const uint32_t AFTER_FFE3_WRITE_MS       = 80;
const uint32_t AFTER_FFE4_WRITE_MS       = 150;

const uint32_t WAIT_FOR_STATUS_12_MS     = 6000;
const uint32_t WAIT_FOR_ACK_14_MS        = 5000;
const uint32_t WAIT_FOR_FIRST_10_MS      = 30000;

// Session-Verhalten
const uint32_t SESSION_MAX_WITHOUT_WEIGHT_MS      = 60000;
const uint32_t SESSION_IDLE_AFTER_FINAL_MS        = 12000;
const uint32_t SESSION_MAX_AFTER_FIRST_FINAL_MS   = 45000;

// Nach erfolgreicher Messung
const bool     SEND_CLEANUP_AFTER_FINAL           = false;
const uint32_t POST_SUCCESS_NOT_SEEN_REQUIRED_MS  = 8000;
const uint32_t POST_SUCCESS_REARM_MAX_WAIT_MS     = 60000;
const uint32_t POST_SUCCESS_REARM_SCAN_CHUNK_SEC  = 1;

// Nach Idle ohne Messung
const uint32_t IDLE_NO_WEIGHT_PAUSE_MS            = 1000;

// Schreiboptionen
const bool FFE3_WRITE_WITH_RESPONSE    = false;
const bool FFE4_WRITE_WITH_RESPONSE    = false;
const bool CLEANUP_WRITE_WITH_RESPONSE = false;

// BLE Client Cleanup
const bool DELETE_CLIENT_AFTER_FAILURE = true;
const bool DELETE_CLIENT_AFTER_SUCCESS = false;

// Notify-Puffer
const uint8_t NOTIFY_BUFFER_SIZE = 16;
const uint8_t NOTIFY_MAX_LENGTH  = 32;

// Live-Logging
const uint32_t LIVE_WEIGHT_LOG_INTERVAL_MS = 5000;
const float LIVE_WEIGHT_LOG_DELTA_KG = 0.20f;

// UUIDs
static NimBLEUUID UUID_SERVICE_FFE0("0000ffe0-0000-1000-8000-00805f9b34fb");
static NimBLEUUID UUID_CHAR_FFE1  ("0000ffe1-0000-1000-8000-00805f9b34fb");
static NimBLEUUID UUID_CHAR_FFE2  ("0000ffe2-0000-1000-8000-00805f9b34fb");
static NimBLEUUID UUID_CHAR_FFE3  ("0000ffe3-0000-1000-8000-00805f9b34fb");
static NimBLEUUID UUID_CHAR_FFE4  ("0000ffe4-0000-1000-8000-00805f9b34fb");

// QN-Scale Zeitbasis
const uint32_t SCALE_UNIX_TIMESTAMP_OFFSET = 946702800UL;


/***************************************************************
 * 2) GLOBALE VARIABLEN
 ***************************************************************/

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

NimBLEScan* pBLEScan = nullptr;
NimBLEClient* pClient = nullptr;

NimBLERemoteCharacteristic* chrFFE1 = nullptr;
NimBLERemoteCharacteristic* chrFFE2 = nullptr;
NimBLERemoteCharacteristic* chrFFE3 = nullptr;
NimBLERemoteCharacteristic* chrFFE4 = nullptr;

bool connected = false;

bool hasStatus12 = false;
bool hasAck14 = false;
bool hasFirst10 = false;
bool hasAnyFinal = false;

float weightScaleFactor = 100.0f;
int lastScaleRssi = 0;

uint32_t lastStatusPublishMs = 0;
uint32_t lastErrorPublishMs = 0;
String lastPublishedStatus = "";
String lastPublishedError = "";

uint32_t lastLiveWeightLogMs = 0;
float lastLiveWeightLoggedKg = -999.0f;

String lastFinalRawInSession = "";

struct NotifyPacket {
  bool used;
  bool isNotify;
  char uuid[40];
  uint8_t length;
  uint8_t data[NOTIFY_MAX_LENGTH];
};

NotifyPacket notifyBuffer[NOTIFY_BUFFER_SIZE];

volatile uint8_t notifyWriteIndex = 0;
volatile uint8_t notifyReadIndex = 0;
volatile uint8_t notifyCount = 0;
volatile uint32_t notifyOverflowCount = 0;

// Session Trace
uint32_t sessionCounter = 0;
uint32_t sessionStartMs = 0;
uint32_t sessionFoundMs = 0;
uint32_t sessionConnectedMs = 0;
uint32_t sessionStatus12Ms = 0;
uint32_t sessionAck14Ms = 0;
uint32_t sessionFirst10Ms = 0;
uint32_t sessionFirstFinalMs = 0;
uint8_t sessionAddressType = 0;
int sessionRssi = 0;
uint16_t sessionUnknownPackets = 0;
uint16_t sessionPackets10 = 0;
uint16_t sessionFinalPublishes = 0;
String sessionLastRaw = "";


/***************************************************************
 * 3) VORDEKLARATIONEN
 ***************************************************************/

void processNotifyQueue();
void processNotifyPacket(const NotifyPacket&amp; packet);


/***************************************************************
 * 4) LOGGING
 ***************************************************************/

String msPrefix() {
  char buf[24];
  snprintf(buf, sizeof(buf), "[%010lu ms] ", (unsigned long)millis());
  return String(buf);
}

void logInfo(const String&amp; msg) {
  Serial.println(msPrefix() + "[INFO] " + msg);
}

void logDebug(const String&amp; msg) {
  if (DEBUG) {
    Serial.println(msPrefix() + "[DEBUG] " + msg);
  }
}

void logWarn(const String&amp; msg) {
  Serial.println(msPrefix() + "[WARN] " + msg);
}

void logError(const String&amp; msg) {
  Serial.println(msPrefix() + "[ERROR] " + msg);
}


/***************************************************************
 * 5) MQTT / WLAN
 ***************************************************************/

void connectWiFi() {
  if (WiFi.status() == WL_CONNECTED) {
    return;
  }

  logInfo("Verbinde WLAN ...");

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  uint32_t startMs = millis();

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");

    if (millis() - startMs &gt; 30000) {
      Serial.println();
      logError("WLAN-Verbindung fehlgeschlagen. Neustart.");
      delay(1000);
      ESP.restart();
    }
  }

  Serial.println();
  logInfo("WLAN verbunden. IP: " + WiFi.localIP().toString());
}

bool mqttPublishRaw(const char* topic, const String&amp; payload, bool retained) {
  if (!mqttClient.connected()) {
    return false;
  }

  bool ok = mqttClient.publish(topic, payload.c_str(), retained);

  if (DEBUG) {
    logDebug(String("MQTT publish ") + topic + " = " + payload + (ok ? " OK" : " FEHLER"));
  }

  return ok;
}

bool mqttPublishStatus(const String&amp; status) {
  uint32_t now = millis();

  if (status == lastPublishedStatus &amp;&amp; now - lastStatusPublishMs &lt; 10000) {
    return true;
  }

  lastPublishedStatus = status;
  lastStatusPublishMs = now;

  return mqttPublishRaw(MQTT_TOPIC_STATUS, status, MQTT_RETAIN_STATUS);
}

bool mqttPublishError(const String&amp; errorText) {
  uint32_t now = millis();

  if (errorText == lastPublishedError &amp;&amp; now - lastErrorPublishMs &lt; 10000) {
    return true;
  }

  lastPublishedError = errorText;
  lastErrorPublishMs = now;

  return mqttPublishRaw(MQTT_TOPIC_ERROR, errorText, MQTT_RETAIN_VALUES);
}

void connectMQTT() {
  if (mqttClient.connected()) {
    return;
  }

  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setBufferSize(1024);

  logInfo("Verbinde MQTT ...");

  bool ok = false;

  if (strlen(MQTT_USER) &gt; 0) {
    ok = mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD);
  } else {
    ok = mqttClient.connect(MQTT_CLIENT_ID);
  }

  if (ok) {
    logInfo("MQTT verbunden.");
    mqttPublishError("");
    mqttPublishStatus("ready");
  } else {
    logWarn("MQTT-Verbindung fehlgeschlagen. State: " + String(mqttClient.state()));
  }
}

void ensureNetwork() {
  connectWiFi();
  connectMQTT();
  mqttClient.loop();
}


/***************************************************************
 * 6) HILFSFUNKTIONEN
 ***************************************************************/

bool isBleConnected() {
  return (pClient != nullptr &amp;&amp; pClient-&gt;isConnected());
}

String bytesToHex(const uint8_t* data, size_t length) {
  String out = "";

  for (size_t i = 0; i &lt; length; i++) {
    if (data[i] &lt; 0x10) {
      out += "0";
    }

    out += String(data[i], HEX);

    if (i &lt; length - 1) {
      out += " ";
    }
  }

  out.toUpperCase();
  return out;
}

uint8_t sumChecksum(const uint8_t* data, size_t start, size_t endExclusive) {
  uint16_t sum = 0;

  for (size_t i = start; i &lt; endExclusive; i++) {
    sum += data[i];
  }

  return (uint8_t)(sum &amp; 0xFF);
}

void writeUInt32LE(uint8_t* target, uint32_t value) {
  target[0] = (uint8_t)(value &amp; 0xFF);
  target[1] = (uint8_t)((value &gt;&gt; 8) &amp; 0xFF);
  target[2] = (uint8_t)((value &gt;&gt; 16) &amp; 0xFF);
  target[3] = (uint8_t)((value &gt;&gt; 24) &amp; 0xFF);
}

float decodeWeightKg(uint8_t highByte, uint8_t lowByte, float scaleFactor) {
  uint16_t raw = ((uint16_t)highByte &lt;&lt; 8) | lowByte;

  if (scaleFactor &lt;= 0.0f) {
    scaleFactor = 100.0f;
  }

  return (float)raw / scaleFactor;
}

uint16_t decodeUInt16BE(uint8_t highByte, uint8_t lowByte) {
  return ((uint16_t)highByte &lt;&lt; 8) | lowByte;
}

bool isKnownScaleAddress(const NimBLEAdvertisedDevice* device) {
  if (device == nullptr) {
    return false;
  }

  String address = device-&gt;getAddress().toString().c_str();
  address.toLowerCase();

  String targetAddress = SCALE_ADDRESS;
  targetAddress.toLowerCase();

  return address == targetAddress;
}

void serviceBleFor(uint32_t durationMs) {
  uint32_t startMs = millis();

  while (millis() - startMs &lt; durationMs) {
    ensureNetwork();

    if (isBleConnected()) {
      processNotifyQueue();
    }

    delay(10);
  }
}

void resetRuntimeFlagsForNewSession() {
  hasStatus12 = false;
  hasAck14 = false;
  hasFirst10 = false;
  hasAnyFinal = false;

  weightScaleFactor = 100.0f;

  lastLiveWeightLogMs = 0;
  lastLiveWeightLoggedKg = -999.0f;

  lastFinalRawInSession = "";
}


/***************************************************************
 * 7) ZEITFUNKTIONEN FÜR FFE4
 ***************************************************************/

int monthNameToNumber(const char* monthName) {
  if (strncmp(monthName, "Jan", 3) == 0) return 1;
  if (strncmp(monthName, "Feb", 3) == 0) return 2;
  if (strncmp(monthName, "Mar", 3) == 0) return 3;
  if (strncmp(monthName, "Apr", 3) == 0) return 4;
  if (strncmp(monthName, "May", 3) == 0) return 5;
  if (strncmp(monthName, "Jun", 3) == 0) return 6;
  if (strncmp(monthName, "Jul", 3) == 0) return 7;
  if (strncmp(monthName, "Aug", 3) == 0) return 8;
  if (strncmp(monthName, "Sep", 3) == 0) return 9;
  if (strncmp(monthName, "Oct", 3) == 0) return 10;
  if (strncmp(monthName, "Nov", 3) == 0) return 11;
  if (strncmp(monthName, "Dec", 3) == 0) return 12;
  return 1;
}

int32_t daysFromCivil(int32_t year, uint32_t month, uint32_t day) {
  year -= month &lt;= 2;

  const int32_t era = (year &gt;= 0 ? year : year - 399) / 400;
  const uint32_t yoe = (uint32_t)(year - era * 400);
  const uint32_t doy = (153 * (month + (month &gt; 2 ? -3 : 9)) + 2) / 5 + day - 1;
  const uint32_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;

  return era * 146097 + (int32_t)doe - 719468;
}

uint32_t getCompileUnixTime() {
  char monthText[4] = {0};
  int day = 1;
  int year = 2000;
  int hour = 0;
  int minute = 0;
  int second = 0;

  sscanf(__DATE__, "%3s %d %d", monthText, &amp;day, &amp;year);
  sscanf(__TIME__, "%d:%d:%d", &amp;hour, &amp;minute, &amp;second);

  int month = monthNameToNumber(monthText);
  int32_t days = daysFromCivil(year, month, day);

  return (uint32_t)days * 86400UL +
         (uint32_t)hour * 3600UL +
         (uint32_t)minute * 60UL +
         (uint32_t)second;
}

uint32_t getScaleTimeFromCompileTime() {
  uint32_t unixTime = getCompileUnixTime();

  if (unixTime &lt;= SCALE_UNIX_TIMESTAMP_OFFSET) {
    return 0;
  }

  return unixTime - SCALE_UNIX_TIMESTAMP_OFFSET;
}


/***************************************************************
 * 8) NOTIFY-PUFFER
 ***************************************************************/

void resetNotifyBuffer() {
  noInterrupts();

  for (uint8_t i = 0; i &lt; NOTIFY_BUFFER_SIZE; i++) {
    notifyBuffer[i].used = false;
    notifyBuffer[i].isNotify = false;
    notifyBuffer[i].uuid[0] = 0;
    notifyBuffer[i].length = 0;

    for (uint8_t j = 0; j &lt; NOTIFY_MAX_LENGTH; j++) {
      notifyBuffer[i].data[j] = 0;
    }
  }

  notifyWriteIndex = 0;
  notifyReadIndex = 0;
  notifyCount = 0;
  notifyOverflowCount = 0;

  interrupts();
}

void pushNotifyPacket(
  NimBLERemoteCharacteristic* pRemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify
) {
  if (pRemoteCharacteristic == nullptr || pData == nullptr) {
    return;
  }

  if (length &gt; NOTIFY_MAX_LENGTH) {
    length = NOTIFY_MAX_LENGTH;
  }

  noInterrupts();

  if (notifyCount &gt;= NOTIFY_BUFFER_SIZE) {
    notifyOverflowCount++;
    interrupts();
    return;
  }

  uint8_t index = notifyWriteIndex;

  notifyBuffer[index].used = true;
  notifyBuffer[index].isNotify = isNotify;
  notifyBuffer[index].length = (uint8_t)length;

  String uuidString = pRemoteCharacteristic-&gt;getUUID().toString().c_str();
  strncpy(notifyBuffer[index].uuid, uuidString.c_str(), sizeof(notifyBuffer[index].uuid) - 1);
  notifyBuffer[index].uuid[sizeof(notifyBuffer[index].uuid) - 1] = 0;

  for (size_t i = 0; i &lt; length; i++) {
    notifyBuffer[index].data[i] = pData[i];
  }

  notifyWriteIndex = (notifyWriteIndex + 1) % NOTIFY_BUFFER_SIZE;
  notifyCount++;

  interrupts();
}

bool popNotifyPacket(NotifyPacket&amp; packet) {
  noInterrupts();

  if (notifyCount == 0) {
    interrupts();
    return false;
  }

  uint8_t index = notifyReadIndex;

  packet = notifyBuffer[index];
  notifyBuffer[index].used = false;

  notifyReadIndex = (notifyReadIndex + 1) % NOTIFY_BUFFER_SIZE;
  notifyCount--;

  interrupts();
  return true;
}

void notifyCallback(
  NimBLERemoteCharacteristic* pRemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify
) {
  pushNotifyPacket(pRemoteCharacteristic, pData, length, isNotify);
}


/***************************************************************
 * 9) SESSION DEBUG
 ***************************************************************/

void resetSessionTrace() {
  sessionCounter++;
  sessionStartMs = millis();
  sessionFoundMs = 0;
  sessionConnectedMs = 0;
  sessionStatus12Ms = 0;
  sessionAck14Ms = 0;
  sessionFirst10Ms = 0;
  sessionFirstFinalMs = 0;
  sessionAddressType = 0;
  sessionRssi = 0;
  sessionUnknownPackets = 0;
  sessionPackets10 = 0;
  sessionFinalPublishes = 0;
  sessionLastRaw = "";
}

uint32_t sinceSession(uint32_t valueMs) {
  if (valueMs == 0 || sessionStartMs == 0) {
    return 0;
  }

  return valueMs - sessionStartMs;
}

String buildSessionJson(const String&amp; resultText) {
  String json = "{";
  json += "\"version\":\"0.4.34\"";
  json += ",\"session\":" + String(sessionCounter);
  json += ",\"result\":\"" + resultText + "\"";
  json += ",\"uptime_ms\":" + String(millis());
  json += ",\"duration_ms\":" + String(sessionStartMs &gt; 0 ? millis() - sessionStartMs : 0);
  json += ",\"rssi\":" + String(sessionRssi);
  json += ",\"addr_type\":" + String(sessionAddressType);
  json += ",\"found_ms\":" + String(sinceSession(sessionFoundMs));
  json += ",\"connected_ms\":" + String(sinceSession(sessionConnectedMs));
  json += ",\"status12_ms\":" + String(sinceSession(sessionStatus12Ms));
  json += ",\"ack14_ms\":" + String(sinceSession(sessionAck14Ms));
  json += ",\"first10_ms\":" + String(sinceSession(sessionFirst10Ms));
  json += ",\"first_final_ms\":" + String(sinceSession(sessionFirstFinalMs));
  json += ",\"packets10\":" + String(sessionPackets10);
  json += ",\"final_publishes\":" + String(sessionFinalPublishes);
  json += ",\"unknown_packets\":" + String(sessionUnknownPackets);
  json += ",\"last_raw\":\"" + sessionLastRaw + "\"";
  json += "}";

  return json;
}

void logSessionSummary(const String&amp; resultText) {
  if (DEBUG &amp;&amp; DEBUG_SESSION_TRACE) {
    String line = "[SESSION #" + String(sessionCounter) + "] " +
                  resultText +
                  " | RSSI=" + String(sessionRssi) +
                  " dBm" +
                  " | AddrType=" + String(sessionAddressType) +
                  " | found=" + String(sinceSession(sessionFoundMs)) + "ms" +
                  " | connected=" + String(sinceSession(sessionConnectedMs)) + "ms" +
                  " | 0x12=" + String(sinceSession(sessionStatus12Ms)) + "ms" +
                  " | 0x14=" + String(sinceSession(sessionAck14Ms)) + "ms" +
                  " | 0x10=" + String(sinceSession(sessionFirst10Ms)) + "ms" +
                  " | firstFinal=" + String(sinceSession(sessionFirstFinalMs)) + "ms" +
                  " | packets10=" + String(sessionPackets10) +
                  " | finalPublishes=" + String(sessionFinalPublishes) +
                  " | unknown=" + String(sessionUnknownPackets);

    logInfo(line);
  }

  mqttPublishRaw(MQTT_TOPIC_SESSION_DEBUG, buildSessionJson(resultText), MQTT_RETAIN_VALUES);
}


/***************************************************************
 * 10) BLE AUFRÄUMEN
 ***************************************************************/

void resetBleReferences(const String&amp; reason) {
  logDebug("BLE-Referenzen zurücksetzen: " + reason);

  chrFFE1 = nullptr;
  chrFFE2 = nullptr;
  chrFFE3 = nullptr;
  chrFFE4 = nullptr;

  connected = false;
}

void cleanupBleClient(const String&amp; reason, bool deleteClient) {
  logDebug("BLE-Cleanup: " + reason);

  if (pClient != nullptr) {
    if (pClient-&gt;isConnected()) {
      pClient-&gt;disconnect();
      delay(200);
    }

    if (deleteClient) {
      NimBLEDevice::deleteClient(pClient);
      pClient = nullptr;
    }
  }

  resetBleReferences(reason);
}

bool sendScaleCleanupCommand() {
  if (!isBleConnected()) {
    return false;
  }

  if (chrFFE3 == nullptr || (!chrFFE3-&gt;canWrite() &amp;&amp; !chrFFE3-&gt;canWriteNoResponse())) {
    return false;
  }

  uint8_t payload[5] = {
    0x1F, 0x05, 0x15, 0x10, 0x49
  };

  logInfo("Sende Cleanup auf FFE3: " + bytesToHex(payload, sizeof(payload)));

  bool ok = chrFFE3-&gt;writeValue(payload, sizeof(payload), CLEANUP_WRITE_WITH_RESPONSE);

  delay(150);
  return ok;
}


/***************************************************************
 * 11) BLE SCHREIBFUNKTIONEN
 ***************************************************************/

bool writeFFE3KgInit() {
  if (!isBleConnected()) {
    return false;
  }

  if (chrFFE3 == nullptr || (!chrFFE3-&gt;canWrite() &amp;&amp; !chrFFE3-&gt;canWriteNoResponse())) {
    return false;
  }

  uint8_t payload[9] = {
    0x13, 0x09, 0x15, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
  };

  payload[8] = sumChecksum(payload, 0, 8);

  logInfo("Schreibe kg-Initialisierung auf FFE3: " + bytesToHex(payload, sizeof(payload)));

  return chrFFE3-&gt;writeValue(payload, sizeof(payload), FFE3_WRITE_WITH_RESPONSE);
}

bool writeFFE4TimeHandshake() {
  if (!isBleConnected()) {
    return false;
  }

  if (chrFFE4 == nullptr || (!chrFFE4-&gt;canWrite() &amp;&amp; !chrFFE4-&gt;canWriteNoResponse())) {
    return false;
  }

  uint32_t scaleTime = getScaleTimeFromCompileTime();

  if (scaleTime == 0) {
    return false;
  }

  uint8_t dateLE[4];

  writeUInt32LE(dateLE, scaleTime);

  uint8_t payload[5] = {
    0x02, dateLE[0], dateLE[1], dateLE[2], dateLE[3]
  };

  logInfo("Schreibe Zeit-Handshake auf FFE4: " + bytesToHex(payload, sizeof(payload)));

  return chrFFE4-&gt;writeValue(payload, sizeof(payload), FFE4_WRITE_WITH_RESPONSE);
}


/***************************************************************
 * 12) MQTT MESSWERT
 ***************************************************************/

void publishFinalMeasurement(float weightKg, uint16_t resistance1, uint16_t resistance2, const String&amp; rawHex) {
  mqttPublishRaw(MQTT_TOPIC_WEIGHT_KG, String(weightKg, 2), MQTT_RETAIN_VALUES);
  mqttPublishRaw(MQTT_TOPIC_FINAL, "true", MQTT_RETAIN_VALUES);
  mqttPublishRaw(MQTT_TOPIC_RESISTANCE_1, String(resistance1), MQTT_RETAIN_VALUES);
  mqttPublishRaw(MQTT_TOPIC_RESISTANCE_2, String(resistance2), MQTT_RETAIN_VALUES);
  mqttPublishRaw(MQTT_TOPIC_RAW_FINAL, rawHex, MQTT_RETAIN_VALUES);
  mqttPublishRaw(MQTT_TOPIC_RSSI, String(lastScaleRssi), MQTT_RETAIN_VALUES);
  mqttPublishRaw(MQTT_TOPIC_LAST_MEASUREMENT, String(millis()), MQTT_RETAIN_VALUES);

  mqttPublishError("");
  mqttPublishStatus("final_measurement_published");

  sessionFinalPublishes++;

  logInfo("Finale Messung per MQTT veröffentlicht.");
}


/***************************************************************
 * 13) NOTIFY AUSWERTUNG
 ***************************************************************/

bool shouldLogLiveWeight(float weightKg, bool finalValue) {
  if (!DEBUG) {
    return false;
  }

  if (finalValue) {
    return true;
  }

  if (!DEBUG_LOG_LIVE_WEIGHT_PACKETS) {
    return false;
  }

  uint32_t now = millis();

  if (lastLiveWeightLoggedKg &lt; -100.0f) {
    lastLiveWeightLoggedKg = weightKg;
    lastLiveWeightLogMs = now;
    return true;
  }

  if (fabs(weightKg - lastLiveWeightLoggedKg) &gt;= LIVE_WEIGHT_LOG_DELTA_KG) {
    lastLiveWeightLoggedKg = weightKg;
    lastLiveWeightLogMs = now;
    return true;
  }

  if (now - lastLiveWeightLogMs &gt;= LIVE_WEIGHT_LOG_INTERVAL_MS) {
    lastLiveWeightLoggedKg = weightKg;
    lastLiveWeightLogMs = now;
    return true;
  }

  return false;
}

void processPacketType12(const NotifyPacket&amp; packet) {
  hasStatus12 = true;

  if (sessionStatus12Ms == 0) {
    sessionStatus12Ms = millis();
  }

  if (packet.length &gt;= 15) {
    uint8_t possibleFactorByte = packet.data[10];

    if (possibleFactorByte == 0x01) {
      weightScaleFactor = 100.0f;
    } else {
      weightScaleFactor = 10.0f;
    }
  } else {
    weightScaleFactor = 100.0f;
  }

  if (DEBUG &amp;&amp; DEBUG_VERBOSE_PACKETS) {
    logDebug("0x12 Statuspaket empfangen. Faktor: " + String((int)weightScaleFactor));
  }
}

void processPacketType14(const NotifyPacket&amp; packet) {
  (void)packet;

  hasAck14 = true;

  if (sessionAck14Ms == 0) {
    sessionAck14Ms = millis();
  }

  if (DEBUG &amp;&amp; DEBUG_VERBOSE_PACKETS) {
    logDebug("0x14 Ack empfangen.");
  }
}

void processPacketType10(const NotifyPacket&amp; packet) {
  sessionPackets10++;
  hasFirst10 = true;

  if (sessionFirst10Ms == 0) {
    sessionFirst10Ms = millis();
  }

  bool finalValue = false;

  if (packet.length &gt;= 6) {
    finalValue = (packet.data[5] == 0x01);
  }

  float weightKg = 0.0f;
  uint16_t resistance1 = 0;
  uint16_t resistance2 = 0;

  if (packet.length &gt;= 5) {
    weightKg = decodeWeightKg(packet.data[3], packet.data[4], weightScaleFactor);
  }

  if (packet.length &gt;= 10) {
    resistance1 = decodeUInt16BE(packet.data[6], packet.data[7]);
    resistance2 = decodeUInt16BE(packet.data[8], packet.data[9]);
  }

  String rawHex = bytesToHex(packet.data, packet.length);
  sessionLastRaw = rawHex;

  if (shouldLogLiveWeight(weightKg, finalValue)) {
    Serial.println();
    Serial.println("--------------------------------------------------");
    Serial.println(msPrefix() + "[BLE] 0x10 Gewichtspaket");
    Serial.println("HEX:      " + rawHex);
    Serial.println("Gewicht:  " + String(weightKg, 2) + " kg");
    Serial.println("Final:    " + String(finalValue ? "JA" : "nein"));
    Serial.println("R1:       " + String(resistance1));
    Serial.println("R2:       " + String(resistance2));
    Serial.println("--------------------------------------------------");
  }

  if (!finalValue) {
    return;
  }

  if (sessionFirstFinalMs == 0) {
    sessionFirstFinalMs = millis();
  }

  hasAnyFinal = true;

  if (rawHex == lastFinalRawInSession) {
    logDebug("Gleiches Finalpaket innerhalb derselben BLE-Session ignoriert.");
    return;
  }

  lastFinalRawInSession = rawHex;

  publishFinalMeasurement(weightKg, resistance1, resistance2, rawHex);
}

void processNotifyPacket(const NotifyPacket&amp; packet) {
  if (DEBUG &amp;&amp; DEBUG_VERBOSE_PACKETS) {
    Serial.println();
    Serial.println("--------------------------------------------------");
    Serial.println(msPrefix() + String(packet.isNotify ? "[BLE] Notify empfangen" : "[BLE] Indication empfangen"));
    Serial.println("Characteristic: " + String(packet.uuid));
    Serial.println("Länge:          " + String(packet.length));
    Serial.println("HEX:            " + bytesToHex(packet.data, packet.length));
    Serial.println("--------------------------------------------------");
  }

  if (packet.length &gt;= 1 &amp;&amp; packet.data[0] == 0x12) {
    processPacketType12(packet);
  } else if (packet.length &gt;= 1 &amp;&amp; packet.data[0] == 0x14) {
    processPacketType14(packet);
  } else if (packet.length &gt;= 1 &amp;&amp; packet.data[0] == 0x10) {
    processPacketType10(packet);
  } else {
    sessionUnknownPackets++;

    if (DEBUG &amp;&amp; DEBUG_UNKNOWN_PACKETS) {
      String typeText = "??";

      if (packet.length &gt;= 1) {
        typeText = String(packet.data[0], HEX);
        typeText.toUpperCase();
      }

      logDebug("[SESSION #" + String(sessionCounter) + "] Unbekanntes Paket: Typ=0x" +
               typeText +
               ", Länge=" + String(packet.length) +
               ", HEX=" + bytesToHex(packet.data, packet.length));
    }
  }
}

void processNotifyQueue() {
  NotifyPacket packet;

  while (popNotifyPacket(packet)) {
    processNotifyPacket(packet);
  }

  if (notifyOverflowCount &gt; 0) {
    uint32_t lost = notifyOverflowCount;
    notifyOverflowCount = 0;

    logWarn("Notify-Pufferüberlauf. Verlorene Pakete: " + String(lost));
    mqttPublishError("Notify-Pufferueberlauf: " + String(lost));
  }
}


/***************************************************************
 * 14) WARTESCHLEIFEN
 ***************************************************************/

bool waitForFlagOrDisconnect(bool* flag, uint32_t timeoutMs, const String&amp; timeoutText, const String&amp; disconnectText, bool publishAsError) {
  uint32_t startMs = millis();

  while (millis() - startMs &lt; timeoutMs) {
    ensureNetwork();
    processNotifyQueue();

    if (*flag) {
      return true;
    }

    if (!isBleConnected()) {
      if (publishAsError) {
        mqttPublishError(disconnectText);
      }

      return false;
    }

    delay(50);
  }

  if (publishAsError) {
    mqttPublishError(timeoutText);
  }

  return false;
}

bool waitForStatus12(uint32_t timeoutMs) {
  return waitForFlagOrDisconnect(
    &amp;hasStatus12,
    timeoutMs,
    "Timeout beim Warten auf 0x12",
    "BLE getrennt beim Warten auf 0x12",
    true
  );
}

bool waitForAck14(uint32_t timeoutMs) {
  return waitForFlagOrDisconnect(
    &amp;hasAck14,
    timeoutMs,
    "Timeout beim Warten auf 0x14",
    "BLE getrennt beim Warten auf 0x14",
    true
  );
}

bool waitForFirst10(uint32_t timeoutMs) {
  return waitForFlagOrDisconnect(
    &amp;hasFirst10,
    timeoutMs,
    "Timeout beim Warten auf 0x10",
    "BLE getrennt beim Warten auf 0x10",
    false
  );
}


/***************************************************************
 * 15) BLE CALLBACKS
 ***************************************************************/

class ClientCallbacks : public NimBLEClientCallbacks {
  void onConnect(NimBLEClient* client) override {
    (void)client;
    connected = true;
  }

  void onDisconnect(NimBLEClient* client, int reason) override {
    (void)client;
    connected = false;

    logWarn("BLE getrennt. Grund: " + String(reason));
  }
};

ClientCallbacks g_clientCallbacks;


/***************************************************************
 * 16) WAAGE SUCHEN
 ***************************************************************/

bool findScaleAddress(String&amp; foundAddress, uint8_t&amp; foundAddressType) {
  foundAddress = "";
  foundAddressType = BLE_ADDR_RANDOM;
  lastScaleRssi = 0;

  uint32_t scanStartMs = millis();

  logInfo("Suche bekannte Waage per BLE-Scan: " + String(SCALE_ADDRESS));
  mqttPublishStatus("scanning");

  while (millis() - scanStartMs &lt; FIND_SCAN_TOTAL_WINDOW_MS) {
    ensureNetwork();

    NimBLEScanResults results = pBLEScan-&gt;getResults(FIND_SCAN_CHUNK_SEC, false);
    uint16_t count = results.getCount();

    if (DEBUG &amp;&amp; DEBUG_VERBOSE_SCAN) {
      logDebug("Scanrunde: Geräte=" + String(count));
    }

    for (uint16_t i = 0; i &lt; count; i++) {
      const NimBLEAdvertisedDevice* device = results.getDevice(i);

      if (device == nullptr) {
        continue;
      }

      if (!isKnownScaleAddress(device)) {
        continue;
      }

      foundAddress = device-&gt;getAddress().toString().c_str();
      foundAddressType = device-&gt;getAddressType();
      lastScaleRssi = device-&gt;getRSSI();

      sessionFoundMs = millis();
      sessionRssi = lastScaleRssi;
      sessionAddressType = foundAddressType;

      logInfo("Bekannte Waage erkannt. Verbinde sofort: " + foundAddress + " RSSI " + String(lastScaleRssi) + " dBm");

      pBLEScan-&gt;clearResults();
      return true;
    }

    pBLEScan-&gt;clearResults();
    delay(1);
  }

  logWarn("Bekannte Waage nicht gefunden.");
  return false;
}

bool isScaleVisibleOnce(uint32_t chunkSec) {
  NimBLEScanResults results = pBLEScan-&gt;getResults(chunkSec, false);
  uint16_t count = results.getCount();

  bool seen = false;

  for (uint16_t i = 0; i &lt; count; i++) {
    const NimBLEAdvertisedDevice* device = results.getDevice(i);

    if (isKnownScaleAddress(device)) {
      seen = true;
      break;
    }
  }

  pBLEScan-&gt;clearResults();
  return seen;
}

void waitAfterSuccessfulMeasurement() {
  logInfo("Passive Re-Arm-Phase: Warte, bis die Waage nicht mehr sichtbar ist.");
  mqttPublishRaw(MQTT_TOPIC_REARM_STATE, "wait_for_not_seen", MQTT_RETAIN_STATUS);
  mqttPublishStatus("post_success_wait_for_not_seen");

  uint32_t waitStartMs = millis();
  uint32_t notSeenStartMs = 0;

  while (millis() - waitStartMs &lt; POST_SUCCESS_REARM_MAX_WAIT_MS) {
    ensureNetwork();

    bool seen = isScaleVisibleOnce(POST_SUCCESS_REARM_SCAN_CHUNK_SEC);

    if (!seen) {
      if (notSeenStartMs == 0) {
        notSeenStartMs = millis();
        logDebug("Passive Re-Arm-Phase: Waage nicht sichtbar, Zeitfenster startet.");
      }

      if (millis() - notSeenStartMs &gt;= POST_SUCCESS_NOT_SEEN_REQUIRED_MS) {
        logInfo("Passive Re-Arm-Phase abgeschlossen. Waage war " +
                String(millis() - notSeenStartMs) +
                " ms zusammenhängend nicht sichtbar.");
        mqttPublishRaw(MQTT_TOPIC_REARM_STATE, "ready", MQTT_RETAIN_STATUS);
        mqttPublishStatus("ready_for_next_measurement");
        return;
      }
    } else {
      if (notSeenStartMs != 0) {
        logDebug("Passive Re-Arm-Phase: Waage wieder sichtbar, Zeitfenster zurückgesetzt.");
      }

      notSeenStartMs = 0;
    }

    delay(20);
  }

  logWarn("Passive Re-Arm-Phase Timeout. Gehe trotzdem wieder in Scan-Bereitschaft.");
  mqttPublishRaw(MQTT_TOPIC_REARM_STATE, "ready_timeout", MQTT_RETAIN_STATUS);
  mqttPublishStatus("ready_for_next_measurement_timeout");
}


/***************************************************************
 * 17) QN-SCALE INITIALISIERUNG
 ***************************************************************/

bool subscribeNotify(NimBLERemoteCharacteristic* chr) {
  if (!isBleConnected()) {
    return false;
  }

  if (chr == nullptr) {
    return false;
  }

  if (!chr-&gt;canNotify()) {
    return false;
  }

  return chr-&gt;subscribe(true, notifyCallback);
}

bool tryConnectWithAddressType(const String&amp; foundAddress, uint8_t addrType) {
  cleanupBleClient("vor Connect-Versuch", DELETE_CLIENT_AFTER_FAILURE);

  pClient = NimBLEDevice::createClient();

  if (pClient == nullptr) {
    return false;
  }

  pClient-&gt;setClientCallbacks(&amp;g_clientCallbacks, false);

  NimBLEAddress address(std::string(foundAddress.c_str()), addrType);

  if (!pClient-&gt;connect(address)) {
    cleanupBleClient("Connect fehlgeschlagen", DELETE_CLIENT_AFTER_FAILURE);
    return false;
  }

  connected = true;
  sessionConnectedMs = millis();

  return true;
}

bool connectScale() {
  resetSessionTrace();
  resetRuntimeFlagsForNewSession();
  resetNotifyBuffer();
  resetBleReferences("vor neuem Verbindungsversuch");

  String foundAddress;
  uint8_t foundAddressType;

  if (!findScaleAddress(foundAddress, foundAddressType)) {
    mqttPublishStatus("scale_not_found");
    logSessionSummary("IDLE: Waage im Scan nicht gefunden");
    return false;
  }

  mqttPublishStatus("connecting");

  bool connectedOk = false;

  if (tryConnectWithAddressType(foundAddress, foundAddressType)) {
    connectedOk = true;
  } else if (foundAddressType != BLE_ADDR_RANDOM &amp;&amp; tryConnectWithAddressType(foundAddress, BLE_ADDR_RANDOM)) {
    connectedOk = true;
  } else if (foundAddressType != BLE_ADDR_PUBLIC &amp;&amp; tryConnectWithAddressType(foundAddress, BLE_ADDR_PUBLIC)) {
    connectedOk = true;
  }

  if (!connectedOk) {
    mqttPublishError("BLE Verbindung fehlgeschlagen");
    mqttPublishStatus("ble_failed");
    logSessionSummary("FAIL: BLE Verbindung fehlgeschlagen");
    return false;
  }

  mqttPublishStatus("ble_connected");

  serviceBleFor(AFTER_CONNECT_DELAY_MS);

  if (!isBleConnected()) {
    mqttPublishStatus("idle_no_weight");
    cleanupBleClient("direkt nach Connect getrennt", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("IDLE: direkt nach Connect getrennt");
    return false;
  }

  NimBLERemoteService* serviceFFE0 = pClient-&gt;getService(UUID_SERVICE_FFE0);

  if (serviceFFE0 == nullptr) {
    mqttPublishError("Service FFE0 nicht gefunden");
    mqttPublishStatus("ble_failed");
    cleanupBleClient("Service FFE0 nicht gefunden", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("FAIL: Service FFE0 nicht gefunden");
    return false;
  }

  serviceBleFor(AFTER_SERVICE_DELAY_MS);

  if (!isBleConnected()) {
    mqttPublishStatus("idle_no_weight");
    cleanupBleClient("nach Service-Suche getrennt", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("IDLE: nach Service-Suche getrennt");
    return false;
  }

  chrFFE1 = serviceFFE0-&gt;getCharacteristic(UUID_CHAR_FFE1);
  chrFFE2 = serviceFFE0-&gt;getCharacteristic(UUID_CHAR_FFE2);
  chrFFE3 = serviceFFE0-&gt;getCharacteristic(UUID_CHAR_FFE3);
  chrFFE4 = serviceFFE0-&gt;getCharacteristic(UUID_CHAR_FFE4);

  if (!subscribeNotify(chrFFE1)) {
    mqttPublishError("FFE1 Subscribe fehlgeschlagen");
    mqttPublishStatus("ble_failed");
    cleanupBleClient("FFE1 Subscribe fehlgeschlagen", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("FAIL: FFE1 Subscribe fehlgeschlagen");
    return false;
  }

  serviceBleFor(AFTER_FFE1_SUBSCRIBE_MS);
  serviceBleFor(AFTER_FFE1_STABILIZE_MS);

  if (!waitForStatus12(WAIT_FOR_STATUS_12_MS)) {
    mqttPublishStatus("ble_failed");
    cleanupBleClient("kein 0x12 Statuspaket", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("FAIL: kein 0x12 Statuspaket");
    return false;
  }

  if (!writeFFE3KgInit()) {
    mqttPublishError("FFE3 fehlgeschlagen");
    mqttPublishStatus("ble_failed");
    cleanupBleClient("FFE3 fehlgeschlagen", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("FAIL: FFE3 fehlgeschlagen");
    return false;
  }

  serviceBleFor(AFTER_FFE3_WRITE_MS);

  if (!waitForAck14(WAIT_FOR_ACK_14_MS)) {
    mqttPublishStatus("ble_failed");
    cleanupBleClient("kein 0x14 Ack", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("FAIL: kein 0x14 Ack");
    return false;
  }

  if (!writeFFE4TimeHandshake()) {
    mqttPublishError("FFE4 fehlgeschlagen");
    mqttPublishStatus("ble_failed");
    cleanupBleClient("FFE4 fehlgeschlagen", DELETE_CLIENT_AFTER_FAILURE);
    logSessionSummary("FAIL: FFE4 fehlgeschlagen");
    return false;
  }

  serviceBleFor(AFTER_FFE4_WRITE_MS);

  mqttPublishStatus("waiting_for_weight10");
  logInfo("BLE-Session aktiv. Warte auf Gewichtsdaten.");

  return true;
}

void runConnectedSession() {
  uint32_t sessionActiveStartMs = millis();

  while (isBleConnected()) {
    ensureNetwork();
    processNotifyQueue();

    uint32_t now = millis();

    if (!hasAnyFinal) {
      if (now - sessionActiveStartMs &gt; SESSION_MAX_WITHOUT_WEIGHT_MS) {
        mqttPublishStatus("idle_no_weight");
        logInfo("Keine Gewichtsdaten innerhalb des Session-Fensters. Vermutlich keine aktive Messung.");

        cleanupBleClient("idle_no_weight: keine Finalmessung", DELETE_CLIENT_AFTER_FAILURE);
        logSessionSummary("IDLE: keine Finalmessung");

        delay(IDLE_NO_WEIGHT_PAUSE_MS);
        return;
      }
    } else {
      if (sessionFirstFinalMs &gt; 0 &amp;&amp; now - sessionFirstFinalMs &gt; SESSION_MAX_AFTER_FIRST_FINAL_MS) {
        if (SEND_CLEANUP_AFTER_FINAL) {
          sendScaleCleanupCommand();
        }

        cleanupBleClient("Finalgewicht: maximale Nachlaufzeit erreicht", DELETE_CLIENT_AFTER_SUCCESS);
        logSessionSummary("OK: Finalgewicht veröffentlicht / max Nachlauf erreicht");

        waitAfterSuccessfulMeasurement();
        return;
      }

      if (sessionFirstFinalMs &gt; 0 &amp;&amp; now - sessionFirstFinalMs &gt; SESSION_IDLE_AFTER_FINAL_MS) {
        if (SEND_CLEANUP_AFTER_FINAL) {
          sendScaleCleanupCommand();
        }

        logInfo("Finalmessung liegt " + String(SESSION_IDLE_AFTER_FINAL_MS) +
                " ms zurück. Warte weiterhin auf Trennung oder neue abweichende Finalpakete.");
      }
    }

    delay(CONNECTED_LOOP_DELAY_MS);
  }

  processNotifyQueue();

  if (hasAnyFinal) {
    logInfo("Waage hat die BLE-Verbindung nach Finalmessung selbst getrennt.");
    cleanupBleClient("Finalgewicht nach abgeschlossener Session", DELETE_CLIENT_AFTER_SUCCESS);
    logSessionSummary("OK: Finalgewicht veröffentlicht");

    waitAfterSuccessfulMeasurement();
    return;
  }

  mqttPublishStatus("idle_no_weight");
  logInfo("BLE getrennt ohne Gewichtsdaten. Vermutlich keine aktive Messung.");
  cleanupBleClient("idle_no_weight: getrennt ohne Gewichtsdaten", DELETE_CLIENT_AFTER_FAILURE);
  logSessionSummary("IDLE: getrennt ohne 0x10");

  delay(IDLE_NO_WEIGHT_PAUSE_MS);
}


/***************************************************************
 * 18) SETUP
 ***************************************************************/

void setup() {
  Serial.begin(115200);
  delay(1500);

  Serial.println();
  Serial.println("RENPHO QN-Scale BLE MQTT Bridge");
  Serial.println("Version 0.4.34");
  Serial.println("ESP32-S3-Zero");
  Serial.println();

  connectWiFi();
  connectMQTT();

  NimBLEDevice::init("ESP32-QNScale-Client");
  NimBLEDevice::setPower(ESP_PWR_LVL_P9);

  pBLEScan = NimBLEDevice::getScan();
  pBLEScan-&gt;setActiveScan(true);
  pBLEScan-&gt;setInterval(45);
  pBLEScan-&gt;setWindow(45);

  mqttPublishStatus("ready");
  mqttPublishRaw(MQTT_TOPIC_REARM_STATE, "ready", MQTT_RETAIN_STATUS);

  logInfo("BLE initialisiert.");
  logInfo("Es wird ausschließlich nach der bekannten Waage gesucht: " + String(SCALE_ADDRESS));
  logInfo("WAIT_FOR_STATUS_12_BEFORE_INIT=true");
  logInfo("AFTER_FFE1_STABILIZE_MS=" + String(AFTER_FFE1_STABILIZE_MS));
  logInfo("SESSION_MAX_WITHOUT_WEIGHT_MS=" + String(SESSION_MAX_WITHOUT_WEIGHT_MS) +
          ", SESSION_IDLE_AFTER_FINAL_MS=" + String(SESSION_IDLE_AFTER_FINAL_MS) +
          ", SESSION_MAX_AFTER_FIRST_FINAL_MS=" + String(SESSION_MAX_AFTER_FIRST_FINAL_MS));
  logInfo("SEND_CLEANUP_AFTER_FINAL=" + String(SEND_CLEANUP_AFTER_FINAL ? "true" : "false"));
  logInfo("Nach Finalmessung: Verbindung offen halten, weitere abweichende Finalpakete veröffentlichen.");
}


/***************************************************************
 * 19) LOOP
 ***************************************************************/

void loop() {
  ensureNetwork();

  bool ok = connectScale();

  if (!ok) {
    logDebug("Kein aktiver Messzyklus. Neuer Versuch in " + String(RECONNECT_PAUSE_MS) + " ms.");
    delay(RECONNECT_PAUSE_MS);
    return;
  }

  runConnectedSession();

  delay(RECONNECT_PAUSE_MS);
}
</code></pre>
]]></description><link>https://forum.iobroker.net/topic/84619/waage-renpho-es-26m-per-mqtt-esp32-anbinden</link><generator>RSS for Node</generator><lastBuildDate>Sun, 24 May 2026 10:58:59 GMT</lastBuildDate><atom:link href="https://forum.iobroker.net/topic/84619.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 23 May 2026 18:14:42 GMT</pubDate><ttl>60</ttl></channel></rss>