<?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[Airthings Wave Plus per MQTT einbinden]]></title><description><![CDATA[<p dir="auto">Moin zusammen,</p>
<p dir="auto">ich habe mein Airthings 2930 Wave Plus erfolgreich per MQTT eingebunden.<br />
Benötigt wird ein ESP32 D1 mini.<br />
Die erforderlichen Biblioteken in ArduinoIDE einbinden, WLAN eintragen und MQTT Daten. Bei Bedarf kann auch eine JSON Struktur aktiviert werden.<br />
Wenn man den Debugmodus aktiviert, kann man per Konsole beobachten was gerade passiert.<br />
Ich übernehme keine Garantie und keine Haftung, der Sketch ist mit Hilfe von ChatGPT entstanden, vielleicht kann es einer von euch gebrauchen.</p>
<p dir="auto">Viele Grüße<br />
Leif</p>
<pre><code>/***************************************************************
 * Airthings Wave Plus -&gt; MQTT Bridge (ESP32 / NimBLE)
 * -------------------------------------------------------------
 * Version:     1.2.7
 * Stand:       2026-04-04
 ***************************************************************/

#include &lt;NimBLEDevice.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;PubSubClient.h&gt;
#include &lt;vector&gt;

/* ============================================================
 * KONFIGURATION
 * ============================================================ */

// ---------- Debug ----------
#define DEBUG_MODE false
#define DEBUG_SHOW_ALL_BLE_DEVICES false
#define DEBUG_MQTT_SINGLE_TOPICS false

// ---------- WLAN ----------
#define WIFI_SSID ""
#define WIFI_PASS ""

// ---------- MQTT ----------
#define MQTT_HOST "192.168.xxx.xxx"
#define MQTT_PORT 1883
#define MQTT_USER ""
#define MQTT_PASS ""
#define MQTT_CLIENT_ID "airthings_waveplus_bridge"
#define MQTT_RETAIN true
#define MQTT_PUBLISH_DELAY_MS 120
#define MQTT_POST_PUBLISH_WAIT_MS 1200
#define WIFI_POST_MQTT_WAIT_MS 1000
#define MQTT_BUFFER_SIZE 512

// ---------- Versandarten ----------
#define ENABLE_SINGLE_TOPICS true
#define ENABLE_JSON_TOPIC    false

// ---------- MQTT Topics ----------
#define TOPIC_RADON_24HR        "airthings/Keller/radon24hour"
#define TOPIC_RADON_LIFETIME    "airthings/Keller/radonLifetime"
#define TOPIC_TEMPERATURE       "airthings/Keller/temperature"
#define TOPIC_HUMIDITY          "airthings/Keller/humidity"
#define TOPIC_PRESSURE          "airthings/Keller/pressure"
#define TOPIC_VOC               "airthings/Keller/voc"
#define TOPIC_CO2               "airthings/Keller/co2"

#define TOPIC_BATTERY_RAW       "airthings/Keller/maybebattery"
#define TOPIC_RUN_COUNTER       "airthings/Keller/runCounter"
#define TOPIC_JSON_SNAPSHOT     "airthings/Keller/json"

// ---------- Timing ----------
#define READ_WAIT_SECONDS         (60 * 60)
#define READ_WAIT_RETRY_SECONDS   30

// Scan-Dauer in Millisekunden
#define BLE_SCAN_DURATION_MS      20000UL
#define BLE_SCAN_END_GRACE_MS     300UL
#define BLE_SCAN_GUARD_EXTRA_MS   5000UL
#define NIMBLE_POST_DEINIT_MS     300UL

// Retry-Logik beim Lesen
#define READ_RETRY_ATTEMPTS       2
#define READ_RETRY_DELAY_MS       1200UL

#define WIFI_CONNECT_WAIT_SECONDS 30
#define WIFI_RETRY_INTERVAL_MS    500

// ---------- Validierung ----------
#define VALIDATE_MEASUREMENTS     true
#define VALID_TEMP_MIN_C          (-40.0f)
#define VALID_TEMP_MAX_C          (85.0f)
#define VALID_HUM_MIN_PERCENT     (0.0f)
#define VALID_HUM_MAX_PERCENT     (100.0f)
#define VALID_PRESSURE_MIN_HPA    (850.0f)
#define VALID_PRESSURE_MAX_HPA    (1150.0f)
#define VALID_CO2_MAX             10000
#define VALID_VOC_MAX             10000
#define INVALID_U16_VALUE         65535U
#define INVALID_HUM_SENTINEL      127.5f

// ---------- Konstanten ----------
#define US_TO_S_FACTOR      1000000ULL
#define SECONDS_TO_MILLIS   1000UL
#define RADON_MAX_VALID     16383
#define MAX_CANDIDATES      20

// Airthings UUIDs
static NimBLEUUID serviceUUID("b42e1c08-ade7-11e4-89d3-123b93f75cba");
static NimBLEUUID currentValuesUUID("b42e2a68-ade7-11e4-89d3-123b93f75cba");

/* ============================================================
 * DATENSTRUKTUREN
 * ============================================================ */

struct AirthingsData {
  bool valid = false;
  bool hasExtendedPayload = false;
  bool measurementPlausible = false;

  float humidity = 0.0f;
  int radon = 0;
  int radonLongTerm = 0;
  float temperature = 0.0f;
  float pressure = 0.0f;
  int co2 = 0;
  int voc = 0;

  uint16_t batteryRawCandidate = 0;

  String invalidReason = "";
};

struct CandidateDevice {
  NimBLEAddress address;
  String addressText;
  int rssi = -999;
  bool hasServiceUuid = false;
  String manufacturerData;
  String name;
};

/* ============================================================
 * GLOBALE VARIABLEN
 * ============================================================ */

std::vector&lt;CandidateDevice&gt; g_candidates;
RTC_DATA_ATTR uint32_t g_runCounter = 0;

/* ============================================================
 * HELFER
 * ============================================================ */

uint16_t unpackU16(uint8_t lowByte, uint8_t highByte) {
  return (static_cast&lt;uint16_t&gt;(highByte) &lt;&lt; 8) | lowByte;
}

int16_t unpackS16(uint8_t lowByte, uint8_t highByte) {
  return static_cast&lt;int16_t&gt;((static_cast&lt;uint16_t&gt;(highByte) &lt;&lt; 8) | lowByte);
}

void deepSleepSeconds(uint32_t seconds) {
  Serial.flush();
  esp_sleep_enable_timer_wakeup(static_cast&lt;uint64_t&gt;(seconds) * US_TO_S_FACTOR);
  esp_deep_sleep_start();
}

String bytesToHexString(const std::string&amp; data) {
  if (data.empty()) return "";

  String out = "";
  const uint8_t* raw = reinterpret_cast&lt;const uint8_t*&gt;(data.data());

  for (size_t i = 0; i &lt; data.length(); i++) {
    if (raw[i] &lt; 16) out += "0";
    out += String(raw[i], HEX);
  }
  out.toUpperCase();
  return out;
}

bool candidateAlreadyExists(const NimBLEAddress&amp; address) {
  for (size_t i = 0; i &lt; g_candidates.size(); i++) {
    if (g_candidates[i].address == address) {
      return true;
    }
  }
  return false;
}

void logMqttPublishResult(const char* topic, const char* payload, bool ok) {
  if (!DEBUG_MQTT_SINGLE_TOPICS) return;

  Serial.print("MQTT publish [");
  Serial.print(topic);
  Serial.print("] = ");
  Serial.print(payload);
  Serial.print(" -&gt; ");
  Serial.println(ok ? "OK" : "FEHLER");
}

bool nearlyEqualFloat(float a, float b, float epsilon = 0.01f) {
  return fabs(a - b) &lt; epsilon;
}

bool validateMeasurements(AirthingsData&amp; result) {
  if (!VALIDATE_MEASUREMENTS) {
    result.measurementPlausible = true;
    return true;
  }

  if (result.co2 == (int)INVALID_U16_VALUE) {
    result.invalidReason = "CO2 invalid sentinel 65535";
    return false;
  }

  if (result.voc == (int)INVALID_U16_VALUE) {
    result.invalidReason = "VOC invalid sentinel 65535";
    return false;
  }

  if (nearlyEqualFloat(result.humidity, INVALID_HUM_SENTINEL)) {
    result.invalidReason = "humidity invalid sentinel 127.5";
    return false;
  }

  if (result.humidity &lt; VALID_HUM_MIN_PERCENT || result.humidity &gt; VALID_HUM_MAX_PERCENT) {
    result.invalidReason = "humidity out of range";
    return false;
  }

  if (result.temperature &lt; VALID_TEMP_MIN_C || result.temperature &gt; VALID_TEMP_MAX_C) {
    result.invalidReason = "temperature out of range";
    return false;
  }

  if (result.pressure &lt; VALID_PRESSURE_MIN_HPA || result.pressure &gt; VALID_PRESSURE_MAX_HPA) {
    result.invalidReason = "pressure out of range";
    return false;
  }

  if (result.co2 &lt; 0 || result.co2 &gt; VALID_CO2_MAX) {
    result.invalidReason = "CO2 out of range";
    return false;
  }

  if (result.voc &lt; 0 || result.voc &gt; VALID_VOC_MAX) {
    result.invalidReason = "VOC out of range";
    return false;
  }

  result.measurementPlausible = true;
  return true;
}

/* ============================================================
 * KANDIDATEN-ERKENNUNG
 * ============================================================ */

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

  if (device-&gt;haveServiceUUID() &amp;&amp; device-&gt;isAdvertisingService(serviceUUID)) {
    return true;
  }

  if (device-&gt;haveManufacturerData()) {
    String mfg = bytesToHexString(device-&gt;getManufacturerData());
    if (mfg.startsWith("34039584A6AE")) {
      return true;
    }
  }

  return false;
}

void addCandidate(const NimBLEAdvertisedDevice* device) {
  if (device == nullptr) return;
  if (g_candidates.size() &gt;= MAX_CANDIDATES) return;
  if (candidateAlreadyExists(device-&gt;getAddress())) return;

  CandidateDevice entry;
  entry.address = device-&gt;getAddress();
  entry.addressText = String(device-&gt;getAddress().toString().c_str());
  entry.rssi = device-&gt;getRSSI();
  entry.hasServiceUuid = device-&gt;haveServiceUUID();
  entry.manufacturerData = device-&gt;haveManufacturerData()
    ? bytesToHexString(device-&gt;getManufacturerData())
    : "";
  entry.name = device-&gt;haveName() ? String(device-&gt;getName().c_str()) : "";

  g_candidates.push_back(entry);

  Serial.print("Added candidate: ");
  Serial.print(entry.addressText);
  Serial.print(" RSSI=");
  Serial.println(entry.rssi);
}

/* ============================================================
 * NIMBLE CALLBACK
 * ============================================================ */

class FoundDeviceCallback : public NimBLEScanCallbacks {
  public:
    void onResult(const NimBLEAdvertisedDevice* device) override {
      if (device == nullptr) return;

      if (DEBUG_SHOW_ALL_BLE_DEVICES) {
        Serial.println("---- BLE device found ----");
        Serial.print("Address: ");
        Serial.println(device-&gt;getAddress().toString().c_str());

        Serial.print("RSSI: ");
        Serial.println(device-&gt;getRSSI());

        Serial.print("Name: ");
        if (device-&gt;haveName()) {
          Serial.println(device-&gt;getName().c_str());
        } else {
          Serial.println("(none)");
        }

        Serial.print("Has service UUID: ");
        Serial.println(device-&gt;haveServiceUUID() ? "yes" : "no");

        Serial.print("Manufacturer data: ");
        if (device-&gt;haveManufacturerData()) {
          Serial.println(bytesToHexString(device-&gt;getManufacturerData()));
        } else {
          Serial.println("(none)");
        }

        Serial.println("--------------------------");
      }

      if (looksLikeAirthingsCandidate(device)) {
        addCandidate(device);
      }
    }
};

FoundDeviceCallback g_foundDeviceCallback;

/* ============================================================
 * SCAN / LESEN
 * ============================================================ */

bool scanForAirthingsCandidates() {
  Serial.println("Scanning for airthings devices");
  g_candidates.clear();

  NimBLEDevice::init("");
  NimBLEScan* scan = NimBLEDevice::getScan();

  if (scan == nullptr) {
    Serial.println("Failed to get NimBLE scan instance.");
    return false;
  }

  scan-&gt;setScanCallbacks(&amp;g_foundDeviceCallback, false);
  scan-&gt;setActiveScan(false);
  scan-&gt;setInterval(100);
  scan-&gt;setWindow(100);
  scan-&gt;setMaxResults(0);
  scan-&gt;clearResults();

  unsigned long scanStartMs = millis();
  Serial.print("BLE scan start, target ms: ");
  Serial.println(BLE_SCAN_DURATION_MS);

  if (!scan-&gt;start(BLE_SCAN_DURATION_MS, false)) {
    Serial.println("Failed to start BLE scan.");
    return false;
  }

  unsigned long guardStart = millis();
  while (scan-&gt;isScanning()) {
    delay(50);

    if (millis() - guardStart &gt; (BLE_SCAN_DURATION_MS + BLE_SCAN_GUARD_EXTRA_MS)) {
      Serial.println("BLE scan timeout guard triggered.");
      scan-&gt;stop();
      break;
    }
  }

  delay(BLE_SCAN_END_GRACE_MS);

  unsigned long scanEndMs = millis();
  Serial.print("BLE scan end, elapsed ms: ");
  Serial.println(scanEndMs - scanStartMs);

  Serial.print("Candidate count after scan: ");
  Serial.println((int)g_candidates.size());

  return !g_candidates.empty();
}

bool parsePayload(const std::string&amp; data, AirthingsData&amp; result) {
  if (data.length() &lt; 16) {
    Serial.print("Payload too short: ");
    Serial.println((unsigned int)data.length());
    return false;
  }

  const uint8_t* raw = reinterpret_cast&lt;const uint8_t*&gt;(data.data());

  result.humidity = raw[1] / 2.0f;

  result.radon = unpackU16(raw[4], raw[5]);
  result.radonLongTerm = unpackU16(raw[6], raw[7]);
  result.temperature = unpackS16(raw[8], raw[9]) / 100.0f;
  result.pressure = unpackU16(raw[10], raw[11]) / 50.0f;
  result.co2 = unpackU16(raw[12], raw[13]);
  result.voc = unpackU16(raw[14], raw[15]);

  if (result.radon &lt;= 0 || result.radon &gt; RADON_MAX_VALID) result.radon = 0;
  if (result.radonLongTerm &lt;= 0 || result.radonLongTerm &gt; RADON_MAX_VALID) result.radonLongTerm = 0;

  if (data.length() &gt;= 20) {
    result.hasExtendedPayload = true;
    result.batteryRawCandidate = unpackU16(raw[18], raw[19]);
  }

  result.valid = true;
  return validateMeasurements(result);
}

bool readFromCandidateOnce(const CandidateDevice&amp; candidate, AirthingsData&amp; result) {
  Serial.print("Connecting to candidate: ");
  Serial.println(candidate.addressText);

  NimBLEClient* client = NimBLEDevice::createClient();
  if (client == nullptr) {
    Serial.println("Failed to create NimBLE client.");
    return false;
  }

  bool success = false;

  do {
    if (!client-&gt;connect(candidate.address)) {
      Serial.println("Failed to connect.");
      break;
    }

    Serial.println("Connected!");
    Serial.println("Retrieving service reference...");

    NimBLERemoteService* remoteService = client-&gt;getService(serviceUUID);
    if (remoteService == nullptr) {
      Serial.println("Candidate does not provide expected service.");
      break;
    }

    Serial.println("Reading radon/temperature/humidity/pressure/CO2/VOC...");

    NimBLERemoteCharacteristic* currentValuesCharacteristic =
      remoteService-&gt;getCharacteristic(currentValuesUUID);

    if (currentValuesCharacteristic == nullptr) {
      Serial.println("Failed to read from the candidate!");
      break;
    }

    std::string data = currentValuesCharacteristic-&gt;readValue();

    if (!parsePayload(data, result)) {
      Serial.print("Failed to parse/validate payload: ");
      Serial.println(result.invalidReason);
      break;
    }

    if (DEBUG_MODE) {
      Serial.print("Humidity: "); Serial.println(result.humidity, 1);
      Serial.print("Radon: "); Serial.println(result.radon);
      Serial.print("Radon long term: "); Serial.println(result.radonLongTerm);
      Serial.print("Temperature: "); Serial.println(result.temperature, 2);
      Serial.print("Pressure: "); Serial.println(result.pressure, 2);
      Serial.print("CO2: "); Serial.println(result.co2);
      Serial.print("VOC: "); Serial.println(result.voc);

      if (result.hasExtendedPayload) {
        Serial.print("Battery raw candidate (experimental): "); Serial.println(result.batteryRawCandidate);
      }
    }

    success = true;

  } while (false);

  if (client-&gt;isConnected()) {
    client-&gt;disconnect();
  }

  NimBLEDevice::deleteClient(client);
  return success;
}

bool readAirthings(AirthingsData&amp; result) {
  Serial.println();

  for (size_t candidateIndex = 0; candidateIndex &lt; g_candidates.size(); candidateIndex++) {
    const CandidateDevice&amp; candidate = g_candidates[candidateIndex];

    Serial.print("Trying candidate ");
    Serial.print((int)(candidateIndex + 1));
    Serial.print("/");
    Serial.println((int)g_candidates.size());

    for (uint8_t attempt = 1; attempt &lt;= READ_RETRY_ATTEMPTS; attempt++) {
      if (DEBUG_MODE) {
        Serial.print("Read attempt ");
        Serial.print(attempt);
        Serial.print("/");
        Serial.println(READ_RETRY_ATTEMPTS);
      }

      if (readFromCandidateOnce(candidate, result)) {
        NimBLEScan* scan = NimBLEDevice::getScan();
        if (scan != nullptr) {
          scan-&gt;clearResults();
        }

        NimBLEDevice::deinit(true);
        delay(NIMBLE_POST_DEINIT_MS);
        return true;
      }

      if (attempt &lt; READ_RETRY_ATTEMPTS) {
        delay(READ_RETRY_DELAY_MS);
      }
    }
  }

  NimBLEScan* scan = NimBLEDevice::getScan();
  if (scan != nullptr) {
    scan-&gt;clearResults();
  }

  NimBLEDevice::deinit(true);
  delay(NIMBLE_POST_DEINIT_MS);
  return false;
}

/* ============================================================
 * WLAN
 * ============================================================ */

bool connectWiFi() {
  Serial.println("Connecting WiFi...");
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  delay(100);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED &amp;&amp;
         millis() - start &lt; (WIFI_CONNECT_WAIT_SECONDS * SECONDS_TO_MILLIS)) {
    delay(WIFI_RETRY_INTERVAL_MS);
    Serial.print(".");
  }
  Serial.println();

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Failed to connect to wifi");
    return false;
  }

  Serial.println("WiFi connected.");
  return true;
}

void disconnectWiFi() {
  delay(WIFI_POST_MQTT_WAIT_MS);
  WiFi.disconnect(true, true);
  WiFi.mode(WIFI_OFF);
}

/* ============================================================
 * MQTT
 * ============================================================ */

bool mqttPublishInt(PubSubClient&amp; mqtt, const char* topic, int value) {
  char payload[16];
  snprintf(payload, sizeof(payload), "%d", value);
  bool ok = mqtt.publish(topic, payload, MQTT_RETAIN);
  mqtt.loop();
  delay(MQTT_PUBLISH_DELAY_MS);
  logMqttPublishResult(topic, payload, ok);
  return ok;
}

bool mqttPublishUInt(PubSubClient&amp; mqtt, const char* topic, uint32_t value) {
  char payload[20];
  snprintf(payload, sizeof(payload), "%lu", (unsigned long)value);
  bool ok = mqtt.publish(topic, payload, MQTT_RETAIN);
  mqtt.loop();
  delay(MQTT_PUBLISH_DELAY_MS);
  logMqttPublishResult(topic, payload, ok);
  return ok;
}

bool mqttPublishFloat(PubSubClient&amp; mqtt, const char* topic, float value, uint8_t decimals) {
  char payload[24];
  dtostrf(value, 0, decimals, payload);

  char* trimmed = payload;
  while (*trimmed == ' ') trimmed++;

  bool ok = mqtt.publish(topic, trimmed, MQTT_RETAIN);
  mqtt.loop();
  delay(MQTT_PUBLISH_DELAY_MS);
  logMqttPublishResult(topic, trimmed, ok);
  return ok;
}

bool mqttPublishJsonSnapshot(PubSubClient&amp; mqtt, const AirthingsData&amp; values) {
  char jsonPayload[320];

  snprintf(
    jsonPayload,
    sizeof(jsonPayload),
    "{\"rc\":%lu,\"r24\":%d,\"rlt\":%d,"
    "\"t\":%.2f,\"h\":%.1f,\"p\":%.2f,\"c\":%d,\"v\":%d,\"b\":%u}",
    (unsigned long)g_runCounter,
    values.radon,
    values.radonLongTerm,
    values.temperature,
    values.humidity,
    values.pressure,
    values.co2,
    values.voc,
    values.batteryRawCandidate
  );

  bool ok = mqtt.publish(TOPIC_JSON_SNAPSHOT, jsonPayload, MQTT_RETAIN);
  mqtt.loop();
  delay(MQTT_PUBLISH_DELAY_MS);

  if (DEBUG_MQTT_SINGLE_TOPICS) {
    Serial.print("MQTT publish [");
    Serial.print(TOPIC_JSON_SNAPSHOT);
    Serial.print("] -&gt; ");
    Serial.println(ok ? "OK" : "FEHLER");
  }

  return ok;
}

bool publishSingleTopics(PubSubClient&amp; mqtt, const AirthingsData&amp; values) {
  bool ok = true;

  ok &amp;= mqttPublishInt(mqtt, TOPIC_RADON_24HR, values.radon);
  ok &amp;= mqttPublishInt(mqtt, TOPIC_RADON_LIFETIME, values.radonLongTerm);
  ok &amp;= mqttPublishFloat(mqtt, TOPIC_TEMPERATURE, values.temperature, 2);
  ok &amp;= mqttPublishFloat(mqtt, TOPIC_HUMIDITY, values.humidity, 1);
  ok &amp;= mqttPublishFloat(mqtt, TOPIC_PRESSURE, values.pressure, 2);
  ok &amp;= mqttPublishInt(mqtt, TOPIC_VOC, values.voc);
  ok &amp;= mqttPublishInt(mqtt, TOPIC_CO2, values.co2);
  ok &amp;= mqttPublishUInt(mqtt, TOPIC_RUN_COUNTER, g_runCounter);

  if (values.hasExtendedPayload) {
    ok &amp;= mqttPublishUInt(mqtt, TOPIC_BATTERY_RAW, values.batteryRawCandidate);
  }

  return ok;
}

bool publishMQTT(const AirthingsData&amp; values) {
  WiFiClient espClient;
  PubSubClient mqtt(espClient);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setBufferSize(MQTT_BUFFER_SIZE);

  if (!mqtt.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS)) {
    Serial.println("Unable to connect/publish to mqtt server.");
    return false;
  }

  bool ok = true;
  bool publishModeValid = false;

  if (ENABLE_SINGLE_TOPICS) {
    publishModeValid = true;
    ok &amp;= publishSingleTopics(mqtt, values);
  }

  if (ENABLE_JSON_TOPIC) {
    publishModeValid = true;
    ok &amp;= mqttPublishJsonSnapshot(mqtt, values);
  }

  if (!publishModeValid) {
    Serial.println("No MQTT publish mode enabled.");
    mqtt.disconnect();
    return false;
  }

  mqtt.loop();
  delay(MQTT_POST_PUBLISH_WAIT_MS);
  mqtt.disconnect();
  delay(300);

  if (!ok) {
    Serial.println("MQTT publish failed.");
    return false;
  }

  Serial.println("MQTT publish complete.");
  return true;
}

/* ============================================================
 * SETUP / LOOP
 * ============================================================ */

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

  g_runCounter++;

  AirthingsData values;
  uint32_t sleepSeconds = READ_WAIT_RETRY_SECONDS;

  if (!scanForAirthingsCandidates()) {
    Serial.printf("\nFAILED to find any Airthings candidate devices. Sleeping for %i seconds before retrying.\n", READ_WAIT_RETRY_SECONDS);
    deepSleepSeconds(READ_WAIT_RETRY_SECONDS);
    return;
  }

  if (!readAirthings(values)) {
    Serial.printf("\nReading FAILED. Sleeping for %i seconds before retrying.\n", READ_WAIT_RETRY_SECONDS);
    deepSleepSeconds(READ_WAIT_RETRY_SECONDS);
    return;
  }

  if (!connectWiFi()) {
    disconnectWiFi();
    Serial.printf("\nReading FAILED. Sleeping for %i seconds before retrying.\n", READ_WAIT_RETRY_SECONDS);
    deepSleepSeconds(READ_WAIT_RETRY_SECONDS);
    return;
  }

  if (publishMQTT(values)) {
    sleepSeconds = READ_WAIT_SECONDS;
    Serial.printf("\nReading complete. Sleeping for %i seconds before taking another reading.\n", READ_WAIT_SECONDS);
  } else {
    sleepSeconds = READ_WAIT_RETRY_SECONDS;
    Serial.printf("\nReading FAILED. Sleeping for %i seconds before retrying.\n", READ_WAIT_RETRY_SECONDS);
  }

  disconnectWiFi();
  delay(500);
  deepSleepSeconds(sleepSeconds);
}

void loop() {
  delay(1);
}
</code></pre>
]]></description><link>https://forum.iobroker.net/topic/84236/airthings-wave-plus-per-mqtt-einbinden</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 16:53:09 GMT</lastBuildDate><atom:link href="https://forum.iobroker.net/topic/84236.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 06 Apr 2026 13:22:09 GMT</pubDate><ttl>60</ttl></channel></rss>