Static snapshot — not a live app. These are saved files, not the running program. Every reading is simulated demo data, frozen at the moment of capture, and the charts are drawn from saved API responses. Buttons, forms and the 5-second live refresh do nothing here. What is for sale is the Flask source you run yourself → · All demos

ESP32 + BH1750 → Flask JSON
Design & Implementation Guide

Monochrome, print-ready documentation. Emphasis on wiring, I²C behavior, timing, and HTTP payloads.

Contents

  1. Overview
  2. Bill of Materials
  3. Wiring & Power
  4. I²C Addressing & Modes
  5. Network & Endpoint
  6. Payload Contract
  7. Build & Flash (Arduino IDE)
  8. Runtime Behavior
  9. Timing & Throughput
  10. Full Sketch
  11. Expected Serial Logs
  12. Troubleshooting
  13. Validation Checklist
  14. Security & Hardening
  15. Extensions & Variations
  16. BH1750 Quick Specs

1) Overview

This firmware reads illuminance (lux) from a BH1750 ambient light sensor over I²C and posts one JSON record to a Flask route every 60 seconds. The sketch initializes I²C on SDA=21 and SCL=22, probes both standard BH1750 addresses (0x23 and 0x5C), performs a single high‑resolution measurement per cycle via readLightLevel(), and submits the lux value as a floating‑point number.

2) Bill of Materials

ItemNotes
ESP32 DevKit (ESP‑WROOM‑32 or equivalent)3.3 V logic, integrated Wi‑Fi
BH1750 light sensor moduleCommon breakouts include pull‑ups and allow 3.3–5 V input
Jumper wiresMale‑female for module headers
USB cablePower and flashing

3) Wiring & Power

Connections

BH1750ESP32Notes
VCC3V3Use 3.3 V on ESP32. Many breakouts tolerate 5 V, but 3.3 V is safest.
GNDGNDCommon ground required.
SDAGPIO 21I²C data line.
SCLGPIO 22I²C clock line.
ADDRGND or 3V3GND → 0x23, 3V3 → 0x5C. Leave floating for 0x23 on many boards.

Most BH1750 modules include ~10 kΩ pull‑ups on SDA/SCL. If using a bare IC, add pull‑ups to 3V3.

Power Notes

  • Typical BH1750 current is ~0.12 mA active. Negligible versus ESP32.
  • Keep I²C runs short. Twisted SDA/GND and SCL/GND pairs help in noisy environments.
  • If multiple I²C devices share the bus, ensure a single effective pull‑up per line.

4) I²C Addressing & Modes

Addresses

  • 0x23: ADDR low/floating (default on most modules).
  • 0x5C: ADDR tied high (to VCC).

The sketch probes both and logs the detected address.

Measurement Mode

  • Continuous High‑Resolution is used. Typical first conversion ~120–180 ms.
  • readLightLevel() returns a float in lux, scaled by internal MTreg.

5) Network & Endpoint

Wi‑Fi SSIDYOUR_WIFI_SSID
Wi‑Fi PasswordYOUR_WIFI_PASSWORD
HTTP MethodPOST
Content‑Typeapplication/json
Endpointhttp://192.168.1.10:5001/add_data
Timeout5 s (client‑side)
IntervalEvery 60 s (one POST per cycle)

6) Payload Contract

The Flask route expects the following JSON keys. plant_id is an integer in this build.

{
  "sensor_type": "bh1750_lux",
  "reading": 123.45,     // float (lux)
  "plant_id": 1          // int
}

Server success responses are HTTP 200 or 201. Other codes are logged to Serial.

7) Build & Flash (Arduino IDE)

  1. Install ESP32 board support via Boards Manager (Espressif Systems).
  2. Libraries:
    • BH1750 by Christopher Laws.
  3. Board: ESP32 Dev Module (or your specific variant).
  4. Set COM/tty port.
  5. Edit User Config block:
    • WIFI_SSID, WIFI_PASS
    • SERVER_URL, SENSOR_TYPE, PLANT_ID
    • I2C_SDA=21, I2C_SCL=22
    • POST_INTERVAL_MS=60000
  6. Compile and upload. Open Serial Monitor at 115200 baud.

8) Runtime Behavior

State Flow

  1. Boot: Start Serial → init I²C → probe BH1750 (0x23 then 0x5C) → connect Wi‑Fi.
  2. Cycle (once per minute):
    1. Single high‑res lux read with readLightLevel().
    2. Clamp negative values to zero; treat NaN as invalid.
    3. POST JSON payload to Flask route.
  3. Connectivity: If Wi‑Fi drops, reconnect before next cycle.

Design Notes

  • No rolling average. One measurement per cycle reduces latency and server write volume.
  • Address probing supports both common module wirings (ADDR low or high).
  • 180 ms delay after begin ensures first valid conversion in continuous mode.

9) Timing & Throughput

ParameterValueNotes
POST interval60 sOne HTTP request per interval
ModeContinuous High‑Resolution~120–180 ms first conversion
HTTP timeout5 sClient abort if server unresponsive
Loop idle50 msCooperative multitasking

10) Full Sketch

/*
  ESP32 DevKit + BH1750 -> Flask JSON (every 60s)
  - Reads lux once per cycle using BH1750::readLightLevel()
  - Posts JSON: {"sensor_type": "...", "reading": <float>, "plant_id": <int>}
  - Pins: SDA=21, SCL=22 (per your hardware)
  - Library: "BH1750" by Christopher Laws (Library Manager)
*/

#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <BH1750.h>

// ---------- USER CONFIG (edit these) ----------
const char* WIFI_SSID   = "YOUR_WIFI_SSID";   // e.g. "YOUR_WIFI_SSID"
const char* WIFI_PASS   = "YOUR_WIFI_PASSWORD";   // e.g. "YOUR_WIFI_PASSWORD"

const char* SERVER_URL  = "http://192.168.1.10:5001/add_data"; // your Flask route
const char* SENSOR_TYPE = "bh1750_lux";                           // keep as given
const int   PLANT_ID    = 1;                                      // adjust for plant

// Post once per minute
constexpr unsigned long POST_INTERVAL_MS = 60UL * 1000UL;

// I2C wiring for ESP32 DevKit (per your note)
constexpr uint8_t I2C_SDA = 21;
constexpr uint8_t I2C_SCL = 22;

// ---------- Globals ----------
BH1750 lightMeter;
unsigned long lastPostMs = 0;

// ---------- Forward decls ----------
void connectWiFi();
void ensureWiFiConnected();
bool initBH1750();
float readLuxOnce();
bool postReading(float lux);

// ---------- Setup ----------
void setup() {
  Serial.begin(115200);
  delay(150);
  Serial.println("\n[BOOT] ESP32 BH1750 -> Flask JSON (60s)");

  // Init I2C on the pins you specified
  Wire.begin(I2C_SDA, I2C_SCL);
  Serial.printf("[I2C] SDA=%u SCL=%u\n", I2C_SDA, I2C_SCL);

  // Bring up sensor. Try 0x23 then 0x5C to cover ADDR pin wiring.
  if (!initBH1750()) {
    Serial.println("[SENSOR] BH1750 init failed. Halting.");
    while (true) { delay(1000); }
  }

  // Wi-Fi
  connectWiFi();

  // Trigger immediate first POST
  lastPostMs = 0;
  Serial.println("[BOOT] Ready.");
}

// ---------- Loop ----------
void loop() {
  ensureWiFiConnected();

  unsigned long now = millis();
  if (lastPostMs == 0 || (now - lastPostMs) >= POST_INTERVAL_MS) {
    lastPostMs = now;

    // Single measurement per your requirement
    float lux = readLuxOnce();
    Serial.printf("[READ] lux=%.2f\n", lux);

    // Push to Flask route
    bool ok = postReading(lux);
    Serial.printf("[POST] %s\n", ok ? "OK" : "FAIL");
  }

  delay(50); // keep CPU cool, maintain responsiveness
}

// ---------- Wi-Fi helpers ----------
void connectWiFi() {
  Serial.printf("[WIFI] Connecting to SSID: %s\n", WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  uint32_t start = millis();
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    if (millis() - start > 20000) { // 20s retry window
      Serial.println("\n[WIFI] Timeout. Re-attempting.");
      WiFi.disconnect(true);
      WiFi.begin(WIFI_SSID, WIFI_PASS);
      start = millis();
    }
  }
  Serial.printf("\n[WIFI] Connected. IP=%s\n", WiFi.localIP().toString().c_str());
}

void ensureWiFiConnected() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("[WIFI] Disconnected. Reconnecting.");
    connectWiFi();
  }
}

// ---------- Sensor helpers ----------
bool initBH1750() {
  // Give sensor power-up time
  delay(150);

  // Try default 0x23 first
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &Wire)) {
    Serial.println("[SENSOR] BH1750 detected at 0x23");
    // Spec: first valid conversion ~120–180 ms in high-res
    delay(180);
    return true;
  }

  // Fallback to 0x5C if ADDR tied high
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x5C, &Wire)) {
    Serial.println("[SENSOR] BH1750 detected at 0x5C");
    delay(180);
    return true;
  }

  Serial.println("[SENSOR] BH1750 not found on 0x23 or 0x5C. Check wiring/power.");
  return false;
}

float readLuxOnce() {
  // Simple single call per your request. No rolling average.
  float lux = lightMeter.readLightLevel(); // returns lux as float
  if (isnan(lux)) {
    Serial.println("[READ] NaN from sensor. Returning -1.");
    return -1.0f;
  }
  if (lux < 0.0f) lux = 0.0f; // clamp negatives
  return lux;
}

// ---------- HTTP POST ----------
bool postReading(float lux) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("[HTTP] No Wi-Fi. Abort.");
    return false;
  }

  WiFiClient client;  // plain HTTP
  HTTPClient http;
  http.setTimeout(5000); // 5s

  if (!http.begin(client, SERVER_URL)) {
    Serial.println("[HTTP] http.begin() failed");
    return false;
  }

  http.addHeader("Content-Type", "application/json");

  // Flask expects: sensor_type (string), reading (float), plant_id (int/string)
  String payload = String("{\"sensor_type\":\"") + SENSOR_TYPE +
                   "\",\"reading\":" + String(lux, 2) +
                   ",\"plant_id\":" + String(PLANT_ID) + "}";

  Serial.print("[HTTP] POST payload: ");
  Serial.println(payload);

  int code = http.POST(payload);
  Serial.printf("[HTTP] Status: %d\n", code);

  String resp = http.getString();
  Serial.print("[HTTP] Response: ");
  Serial.println(resp);

  http.end();
  return (code == 201 || code == 200);
}

11) Expected Serial Logs

[BOOT] ESP32 BH1750 -> Flask JSON (60s)
[I2C] SDA=21 SCL=22
[SENSOR] BH1750 detected at 0x23
[WIFI] Connecting to SSID: YOUR_WIFI_SSID
....
[WIFI] Connected. IP=192.168.1.xxx
[BOOT] Ready.
[READ] lux=123.45
[HTTP] POST payload: {"sensor_type":"bh1750_lux","reading":123.45,"plant_id":1}
[HTTP] Status: 201
[HTTP] Response: {"success": true}
[POST] OK

12) Troubleshooting

No Sensor Detected

  • Run an I²C scanner to confirm address (0x23 or 0x5C).
  • Check SDA/SCL orientation; swap if needed.
  • Verify ADDR pin state matches expected address.

Unstable Lux Values

  • Shield sensor from direct LED flicker; try diffusing cover.
  • Use continuous high‑res mode; avoid rapid re‑init.
  • Optionally median‑filter on server side.

Wi‑Fi / HTTP Failures

  • Check credentials and 2.4 GHz availability.
  • Ping the Flask host from another device.
  • Inspect server logs for request errors.

Units & Scaling

  • BH1750 outputs lux directly; no extra scaling needed with standard MTreg.
  • Clamp negative or NaN readings as the sketch does.

13) Validation Checklist

14) Security & Hardening

15) Extensions & Variations

16) BH1750 Quick Specs

PropertyValue
Illuminance range~1 to 65,535 lux
Resolution~1 lux (high‑res), ~4 lux (low‑res)
Supply2.4–3.6 V IC (many modules accept 3.3–5 V)
InterfaceI²C up to 400 kHz
Typical current~0.12 mA active
Addresses0x23 (ADDR low), 0x5C (ADDR high)

This document is intentionally black‑and‑white for clean printing and photocopying.