Copy
#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");
}
}
}