Simple Dough Rise Detector with ESP8266 & VL53L1X

Below I’ll explain how I built a simple “smart” sensor to monitor when dough has risen enough. As soon as the dough reaches the desired height, the ESP8266 sends an alarm signal and triggers a webhook, in my case associated with Home Assistant. This is particularly convenient if you bake frequently and need to notice right away when the dough is ready for the next step.

Project Idea

The main idea is to use a distance sensor that continuously (or at set intervals) measures the distance to the dough surface in the bowl. When the distance shrinks to a certain defined value, it means the dough has risen, and it’s time to continue.

Then, the ESP8266 board:

  1. Sends an alert via a Telegram bot.
  2. Sends a webhook request to Home Assistant (or another automation service), which can trigger a buzzer or another type of notification.

Hardware Setup

1. ESP8266 Module

I used an ESP8266 board purchased from Aliexpress: ESP8266 on AliExpress

In essence, any ESP8266 variant (NodeMCU, Wemos D1 mini, etc.) would work, as long as it can connect an I2C sensor and has Wi-Fi connectivity.

2. Distance Sensor

I used a VL53L1X-based module for measuring distance: VL53L1X on AliExpress

Main advantages:

  • It’s quite compact.
  • Can measure up to 4m (though realistically less, but enough for a bowl).
  • Operates over I2C.
  • Easily available libraries.
3. Additional Components
  • A breadboard or soldering for reliable connections.
  • Several jumper wires (female-male, male-male) to connect the sensor to the ESP8266.
  • A USB cable and a power supply for the ESP8266.

Connection Diagram

For the VL53L1X module, the usual pins are:

  • VCC (3.3V or 5V) — connect to 3.3V (the ESP8266 provides 3.3V).
  • GND — common ground, connect to the ESP8266’s GND.
  • SDA — connect to D5 (SDA) on the ESP8266 (or your board’s SDA pin).
  • SCL — connect to D6 (SCL) on the ESP8266 (or your board’s SCL pin).

In the example code, I have SHUTDOWN_PIN and INTERRUPT_PIN (pins 2 and 3), but you can omit them if you don’t use hardware interrupts or power-shutdown features on the sensor.

Firmware Setup

Below is my code for the ESP8266 firmware, which:

  1. Connects to Wi-Fi.
  2. Sets up a Telegram bot (with SSL).
  3. Uses VL53L1X for distance measurement.
  4. Sends a notification through a Home Assistant webhook when the dough reaches the desired level.
  5. Optionally sends intermediate data to Telegram.
/* 
  Set true if you want use external library for SSL connection instead ESP32@WiFiClientSecure 
  For example https://github.com/OPEnSLab-OSU/SSLClient/ is very efficient BearSSL library.
  You can use AsyncTelegram2 even with other MCUs or transport layer (ex. Ethernet)
  With SSLClient, be sure "certificates.h" file is present in sketch folder
*/ 
#define USE_CLIENTSSL true  

#include <AsyncTelegram2.h>
#include <Wire.h>
#include <VL53L1X.h>

VL53L1X sensor;

// Optional interrupt and shutdown pins.
#define SHUTDOWN_PIN 2
#define INTERRUPT_PIN 3

// Timezone definition
#include <time.h>
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"

#ifdef ESP8266
  #include <ESP8266WiFi.h>
  #include <ESP8266HTTPClient.h>
  BearSSL::WiFiClientSecure client;
  BearSSL::Session   session;
  BearSSL::X509List  certificate(telegram_cert);
  
#elif defined(ESP32)
  #include <WiFi.h>
  #include <WiFiClient.h>
  #if USE_CLIENTSSL
    #include <SSLClient.h>  
    #include "tg_certificate.h"
    WiFiClient base_client;
    SSLClient client(base_client, TAs, (size_t)TAs_NUM, A0, 1, SSLClient::SSL_ERROR);
  #else
    #include <WiFiClientSecure.h>
    WiFiClientSecure client;  
  #endif
#endif

AsyncTelegram2 myBot(client);

const char* ssid  =  "YOUR_SSID";         // Wi-Fi network SSID
const char* pass  =  "YOUR_WIFI_PASSWORD"; // Wi-Fi network password
const char* token =  "YOUR_BOT_TOKEN";      // Telegram bot token

// Find your userID via @JsonDumpBot or @getidsbot
int64_t userid = "YOUR_USER_ID";

// Webhook URL for Home Assistant
const char* alertURL = "YOUR_ALERT_WEBHOOK";

// Trigger threshold
int alertDistance = -1;
int minDistance = -1;
int alertCount = 0;
uint32_t lastAlertTime = -1;

void sendAlert() {
    HTTPClient http;
    std::unique_ptr<BearSSL::WiFiClientSecure> cc(new BearSSL::WiFiClientSecure);
    cc->setInsecure();
    http.begin(*cc, alertURL); // Specify the webhook URL
    http.addHeader("Content-Type", "application/json"); // Set content type

    // JSON payload
    String payload = "{\"key1\":\"value1\",\"key2\":\"value2\"}";

    int httpResponseCode = http.POST(payload); // Send POST request
    if (httpResponseCode > 0) {
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
      Serial.println("Response payload: " + http.getString());
    } else {
      Serial.print("Error sending POST: ");
      Serial.println(http.errorToString(httpResponseCode).c_str());
    }
    http.end();
}

void blinkLed(int cnt) {
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
  digitalWrite(LED_BUILTIN, HIGH);
  for (int i = 0; i < cnt; ++i) {
    delay(250);
    digitalWrite(LED_BUILTIN, LOW);
    delay(250);
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);

  // Initialize I2C and the sensor
  Wire.begin(D5, D6); // SDA, SCL
  Wire.setClock(400000);
  sensor.setTimeout(500);
  if (!sensor.init()) {
    Serial.println("Failed to detect and initialize sensor!");
    blinkLed(3);
    while (1);
  }

  sensor.setDistanceMode(VL53L1X::Long);
  sensor.setMeasurementTimingBudget(50000);
  sensor.startContinuous(1000);

  Serial.println("Sensor started");

  // Connect to Wi-Fi
  Serial.println("\nConnecting to Wi-Fi...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  delay(500);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    blinkLed(1);
  }
  Serial.println("\nWi-Fi connected!");

#ifdef ESP8266
  // Sync time with NTP to verify Telegram SSL certificates
  configTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
  // Set certificates and client properties
  client.setSession(&session);
  client.setTrustAnchors(&certificate);
  client.setBufferSizes(1024, 1024);
#elif defined(ESP32)
  configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
  #if USE_CLIENTSSL == false
    client.setCACert(telegram_cert);
  #endif
#endif

  // Telegram setup
  myBot.setUpdateTime(2000);
  myBot.setTelegramToken(token);

  Serial.print("\nTesting Telegram connection... ");
  myBot.begin() ? Serial.println("OK") : Serial.println("NOK");

  // Welcome message
  Serial.println("Sending welcome message");
  char welcome_msg[128];
  snprintf(welcome_msg, 128, "BOT @%s online\nSend distance in cm", myBot.getBotName());
  myBot.sendTo(userid, welcome_msg);
}

TBMessage msg;

void loop() {

  // Blink LED to indicate activity
  static uint32_t ledTime = millis();
  if (millis() - ledTime > 250) {
    ledTime = millis();
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }

  // Check for incoming Telegram messages
  if (myBot.getNewMessage(msg)) {
    String message;
    message += "Message from @";
    message += myBot.getBotName();
    message += ":\n";
    message += msg.text;
    Serial.println(message);

    // Convert the received text into a number (threshold distance)
    alertDistance = atoi(msg.text.c_str());

    // Send a reply
    char reply_msg[128];
    snprintf(reply_msg, 128, "Set alert distance to %icm", alertDistance);
    myBot.sendMessage(msg, reply_msg);
    Serial.println(reply_msg);

    // Reset minimum distance and alert count
    minDistance = -1;
    alertCount = 0;
  }

  // If threshold is set, read distance
  if (alertDistance != -1) {
    int distance = (sensor.read() + 9) / 10; // Slight scaling/rounding

    // Track minimum distance
    if (minDistance == -1 || distance < minDistance) {
      minDistance = distance;

      char reply_msg[128];
      snprintf(reply_msg, 128, "Current distance %icm", distance);
      myBot.sendMessage(msg, reply_msg);
      Serial.println(reply_msg);
    }

    // If distance is at or below the threshold
    if (minDistance <= alertDistance) {
      Serial.println("Alert mode");
      // Send alert up to 5 times, every 30 seconds
      if ((alertCount == 0) || (alertCount < 5 && millis() - lastAlertTime > 30000)) {
        alertCount++;
        sendAlert();
        lastAlertTime = millis();
      }
    }
  }

  // Small pause to avoid excessive readings
  delay(5000);
}

Usage Steps

  1. Assemble the circuit:

    • Connect the VL53L1X to the ESP8266 (SDA/SCL, VCC/GND).
    • Power the ESP8266 via USB (computer or power adapter).
  2. Upload the code to the ESP8266 using Arduino IDE or PlatformIO.

    • Make sure you select the correct board (Generic ESP8266, NodeMCU, Wemos D1 mini, etc.).
    • Install the required libraries (AsyncTelegram2, VL53L1X, BearSSL or SSLClient).
  3. Run the ESP8266. You should see it attempt to connect to Wi-Fi, then Telegram, in the Serial Monitor.

  4. Configure your Telegram bot (if you haven’t already). Get your bot token from BotFather and replace YOUR_BOT_TOKEN.

  5. Send the bot a message in Telegram with a number, e.g., “5,” which sets the threshold in centimeters.

  6. Place the sensor above the dough so it measures straight down to the dough surface. The distance from the sensor to the bowl’s bottom should be comfortably within range (tens of centimeters).

  7. When the dough rises and reaches that threshold, the ESP8266:

    • Sends an alert via Telegram,
    • Triggers a webhook in Home Assistant,
    • Optionally you can configure Home Assistant to do anything you like (sound an alarm, turn on a light, send a push notification, etc.).
Bypassing Silent Mode with Critical Alerts

If you want to make sure you never miss the notification (for example, if your phone is on silent), you can use a critical alert in Home Assistant. Here’s an example YAML automation that triggers on a webhook and sends a high-priority notification to your phone:

alias: DiPhone Alert
description: ""
triggers:
  - trigger: webhook
    allowed_methods:
      - POST
      - PUT
    local_only: true
    webhook_id: "XXXXX"  # Replace with your actual webhook_id
conditions: []
actions:
  - action: notify.mobile_app_diphone
    metadata: {}
    data:
      title: Dough Ready!
      message: Wakeup! Dough ready!
      data:
        push:
          sound:
            name: Choo_Choo.caf
            critical: 1
            volume: 1
mode: single

By setting critical: 1 and volume: 1, this notification will override Do Not Disturb or silent mode settings on most mobile devices, ensuring you don’t miss that crucial moment when the dough is ready.

Results and Further Ideas

  • I got a simple automatic “alert” device — as soon as it detects that the distance has shrunk to the set value (meaning the dough has risen), it sends a notification.
  • You can add an OLED display to visually monitor how much the dough has risen.
  • For higher reliability and data tracking, you can log or graph the dough level in Home Assistant.
  • You could integrate with voice assistants (e.g., ask “How long until the dough is fully risen?” and get a response).

In short, with fairly inexpensive hardware (an ESP8266 and a VL53L1X sensor) and a small amount of code, you get a handy solution for dough readiness notifications. The main thing is to position the sensor so it doesn’t get splashed or dirty from the dough (it’s recommended to place it higher up or provide some form of protection). Hopefully, this short guide will help you build something similar or inspire you to create other sensors to monitor not just dough, but many other processes in cooking or around the house!