Arduino-Code für die vernetzte Untertasse

Hier findest du den Arduino-Code für die vernetzte Untertasse. Wenn du zwei Geräte baust, musst du den Code für jedes Gerät einmal anpassen.

– Kopiere den folgenden Code.
– Öffne die Arduino IDE und füge den Code für jedes Gerät in ein neues Sketch-Fenster ein.
– Gehe zurück zur Anleitung  und fahre mit Schritt 5 (Geräterolle festlegen) fort, um den Code weiter anzupassen und auf den ESP32_C3 hochzuladen.



#define ROLE_COFFEE 1   // <-- set to 1 for Device 1, 2 for Device 2

#include <WiFi.h>
#include <DNSServer.h>
#include <WebServer.h>
#include <WiFiManager.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define LED_PIN       4 // GPIO4
#define NUM_LEDS      1
#define DS18B20_PIN   8 // GPIO8

const char* MQTT_SERVER = "broker.emqx.io";
const uint16_t MQTT_PORT = 1883;
const float  THRESHOLD   = 25.0; // peer temp above this → LED on
const unsigned long WATCHDOG_MS = 5000;       // if no peer updates for 5s, switch LED off (and “latch” it off)
const unsigned long PUBLISH_EVERY_MS = 2000;  // publish your own temperature every 2s.

// Wi-Fi behavior knobs
const uint16_t WIFI_CONNECT_TIMEOUT_S   = 10;   // try saved Wi-Fi for ~10s
const uint16_t PORTAL_TIMEOUT_S         = 180;  // captive portal open time (3 min) before giving up
const uint32_t WIFI_RUNTIME_GRACE_MS    = 10000; // if disconnected >10s at runtime, trigger portal/reconnect flow

#if (ROLE_COFFEE==1)
  const char* THIS_NAME   = "coffee1";
  const char* PEER_NAME   = "coffee2";
#elif (ROLE_COFFEE==2)
  const char* THIS_NAME   = "coffee2";
  const char* PEER_NAME   = "coffee1";
#else
  #error "Set ROLE_COFFEE to 1 or 2"
#endif


#define UNIQUE_ID "YOUR_UNIQUE_NAME"  // <-- Ersetze YOUR_UNIQUE_NAME durch einen eigenen, möglichst einzigartigen Namen.z.B.anna_coffee_4821

String TOPIC_THIS_TEMP   = String(UNIQUE_ID) + "/" + THIS_NAME + "/temp";
String TOPIC_THIS_STATUS = String(UNIQUE_ID) + "/" + THIS_NAME + "/status";
String TOPIC_PEER_TEMP   = String(UNIQUE_ID) + "/" + PEER_NAME + "/temp";
String TOPIC_PEER_STATUS = String(UNIQUE_ID) + "/" + PEER_NAME + "/status";

WiFiClient espClient;
PubSubClient mqtt(espClient);
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
OneWire oneWire(DS18B20_PIN);
DallasTemperature sensors(&oneWire);

// --- WiFiManager is global so we can reuse it at runtime ---
WiFiManager wm;
String apName; // e.g., "coffee1-Setup"

String clientId; // unique MQTT ID per device (built from MAC)
unsigned long lastPeerMsg = 0; // timestamp of last peer message (for watchdog)
unsigned long lastPublish = 0;
bool offLatched = false; // latch LED off until new good data arrives

// runtime Wi-Fi drop tracking
unsigned long wifiDropSince = 0;

// A tiny hand-rolled JSON extractor: finds "temp_c":; returns NAN if not found.
float parseTemp(const char* payload) {
  const char* p = strstr(payload, "temp_c");
  if (!p) return NAN;
  p = strchr(p, ':'); if (!p) return NAN; p++;
  return atof(p);
}

// Turns the single NeoPixel red when on, clears it when off, and logs to Serial.
void setLed(bool on) {
  if (on) strip.setPixelColor(0, strip.Color(255, 255, 0)); //yellow
  else strip.clear();
  strip.show();
  Serial.print("["); Serial.print(THIS_NAME); Serial.print("][LED] ");
  Serial.println(on ? "ON" : "OFF");
}

// reacting to peer updates
void mqttCallback(char* topic, byte* payload, unsigned int len) {
  // Copy payload into a null-terminated buf
  char buf[128];
  len = min(len, (unsigned int)(sizeof(buf) - 1));
  memcpy(buf, payload, len); buf[len] = '\0';

  if (strcmp(topic, TOPIC_PEER_TEMP.c_str()) == 0) {
    float t = parseTemp(buf);
    if (!isnan(t)) {
      lastPeerMsg = millis();
      offLatched = false;  // new valid data clears latch
      bool on = (t > THRESHOLD);
      setLed(on);
      Serial.print("["); Serial.print(THIS_NAME); Serial.print("][MQTT] RCV ");
      Serial.print(PEER_NAME); Serial.print(" temp: ");
      Serial.print(t); Serial.println(" °C");
    }
  } else if (strcmp(topic, TOPIC_PEER_STATUS.c_str()) == 0) {
    Serial.print("["); Serial.print(THIS_NAME); Serial.print("][MQTT] ");
    Serial.print(PEER_NAME); Serial.print(" status: "); Serial.println(buf);
    if (strcmp(buf, "offline") == 0) {
      setLed(false);
      offLatched = true;  // latch off until new good data
    }
  }
}

// MQTT (re)connect with LWT
void mqttReconnect() {
  while (!mqtt.connected() && WiFi.status() == WL_CONNECTED) {
    Serial.print("["); Serial.print(THIS_NAME); Serial.println("][MQTT] reconnect...");
    if (mqtt.connect(
          clientId.c_str(),
          nullptr, nullptr,
          TOPIC_THIS_STATUS.c_str(), 1, true,
          "offline"  // LWT
        )) {
      Serial.print("["); Serial.print(THIS_NAME); Serial.println("][MQTT] connected");
      mqtt.subscribe(TOPIC_PEER_TEMP.c_str());
      mqtt.subscribe(TOPIC_PEER_STATUS.c_str());
      mqtt.publish(TOPIC_THIS_STATUS.c_str(), "online", true);
    } else {
      Serial.print("["); Serial.print(THIS_NAME); Serial.print("][MQTT] rc=");
      Serial.println(mqtt.state());
      delay(1000);
    }
  }
}

// --- Wi-Fi ensure function: silent reconnect → portal → restart ---
void ensureWiFiConnected() {
  if (WiFi.status() == WL_CONNECTED) {
    wifiDropSince = 0;
    return;
  }

  // Start/continue timing the drop
  if (wifiDropSince == 0) wifiDropSince = millis();

  // Only trigger the heavy flow if we've been down for more than the grace period
  if (millis() - wifiDropSince < WIFI_RUNTIME_GRACE_MS) return;

  Serial.print("["); Serial.print(THIS_NAME);
  Serial.println("][WiFi] Disconnected >10s → trying autoConnect/portal");

  wm.setCaptivePortalEnable(true);
  wm.setConfigPortalBlocking(true);
  wm.setAPClientCheck(false);
  wm.setBreakAfterConfig(true);

  wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT_S); // try saved Wi-Fi for ~10s
  wm.setConfigPortalTimeout(PORTAL_TIMEOUT_S);  // then portal for 3 min

  bool ok = wm.autoConnect(apName.c_str()); // tries saved creds; if fail → opens portal
  if (!ok || WiFi.status() != WL_CONNECTED) {
    Serial.print("["); Serial.print(THIS_NAME);
    Serial.println("][WiFi] Still not connected after portal → restarting");
    delay(2000);
    ESP.restart();
  }

  // Connected again
  Serial.print("["); Serial.print(THIS_NAME); Serial.print("][WiFi] Reconnected, IP: ");
  Serial.println(WiFi.localIP());
  wifiDropSince = 0;
}

// Initialize serial, LED, DS18B20, and Wi-Fi (with auto portal fallback)
void setup() {
  Serial.begin(115200);
  strip.begin(); strip.show();
  sensors.begin();

  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false);

  // Build AP name, e.g., "coffee1-Setup"
  apName = String(THIS_NAME) + "-Setup";
  Serial.print("["); Serial.print(THIS_NAME); Serial.print("][WiFi] Portal ");
  Serial.println(apName);

  // Configure WiFiManager once and perform the full flow on boot:
  wm.setCaptivePortalEnable(true);
  wm.setConfigPortalBlocking(true);
  wm.setAPClientCheck(false);
  wm.setBreakAfterConfig(true);

  wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT_S); // try saved Wi-Fi for ~10s
  wm.setConfigPortalTimeout(PORTAL_TIMEOUT_S);  // portal for 3 minutes max

  // autoConnect: try saved creds; if fail within connect timeout → opens portal
  bool ok = wm.autoConnect(apName.c_str());
  if (!ok || WiFi.status() != WL_CONNECTED) {
    Serial.print("["); Serial.print(THIS_NAME);
    Serial.println("][WiFi] No Wi-Fi after portal → restarting");
    delay(2000);
    ESP.restart();
  }

  // MQTT setup
  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setCallback(mqttCallback);

  // Build a unique MQTT client ID from device MAC
  uint64_t mac = ESP.getEfuseMac();
  char id[32];
  snprintf(id, sizeof(id), "%s-%04X%04X", THIS_NAME, (uint16_t)(mac >> 16), (uint16_t)mac);
  clientId = id;

  Serial.print("["); Serial.print(THIS_NAME); Serial.print("][WiFi] SSID: ");
  Serial.println(WiFi.SSID());
  Serial.print("["); Serial.print(THIS_NAME); Serial.print("][WiFi] IP: ");
  Serial.println(WiFi.localIP());
}

// keep MQTT alive, watchdog, publish local temp, and maintain Wi-Fi
void loop() {
  // Maintain Wi-Fi; may open portal and even restart if necessary
  ensureWiFiConnected();

  if (WiFi.status() == WL_CONNECTED) {
    if (!mqtt.connected()) mqttReconnect();
    mqtt.loop();
  }

  unsigned long now = millis();

  // Watchdog: if peer silent -> LED OFF (latch)
  if (lastPeerMsg > 0 && (now - lastPeerMsg > WATCHDOG_MS) && !offLatched) {
    Serial.print("["); Serial.print(THIS_NAME);
    Serial.println("][WATCHDOG] No peer data -> LED OFF");
    setLed(false);
    offLatched = true;
  }

  // Publish THIS device temp every 2 seconds
  if (now - lastPublish >= PUBLISH_EVERY_MS) {
    lastPublish = now;
    sensors.requestTemperatures();
    float tLocal = sensors.getTempCByIndex(0);

    // Optional: filter out disconnected reading (-127.00)
    if (tLocal > -100.0f) {
      char tbuf[24]; dtostrf(tLocal, 0, 2, tbuf);
      char json[64]; snprintf(json, sizeof(json), "{\"temp_c\":%s}", tbuf);
      if (WiFi.status() == WL_CONNECTED && mqtt.connected()) {
        mqtt.publish(TOPIC_THIS_TEMP.c_str(), json, false);
      }
      Serial.print("["); Serial.print(THIS_NAME); Serial.print("][MQTT] PUB ");
      Serial.print(THIS_NAME); Serial.print(" temp: ");
      Serial.print(tLocal); Serial.println(" °C");
    } else {
      Serial.print("["); Serial.print(THIS_NAME);
      Serial.println("][TEMP] Sensor disconnected/read error");
    }
  }
}

Zielgruppe: Familie, Paare, Freunde
Art: DIT
Zeitlicher Aufwand: Mittel
Schwierigkeitsgrad: Mittel

Materialien:
Hardware-Komponenten (z.B. ESP32_C3, LED, Temperatursensor)
Computer
Webbrowser
Internet

Idee & Inspiration:
Kaffeepause über Distanz

Anleitung:
Bau deine eigene vernetzte Untertasse