Home Assistant multisensor on an ESP32-C3
Temperature, humidity, light and motion on one ESP32-C3, the room sensor Home Assistant users build first, with each reading printed as the MQTT message it would send. The whole build, an ESP32-C3 and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate Runs in your browser. Free, and no account needed.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Home Assistant multisensor: temperature, humidity, light and motion.
//
// The four readings people put in nearly every room, on one ESP32-C3:
// DHT22 on GPIO 3 temperature and humidity, read every 10 seconds
// BH1750 on I2C light in lux (SDA GPIO 4, SCL GPIO 5)
// PIR on GPIO 18 motion, reported the moment it changes
// LED on GPIO 19 lights while the PIR sees motion
//
// On a real device you would most likely flash ESPHome and write YAML instead
// of this sketch. Mokxi runs Arduino code, not ESPHome, so this sketch does by
// hand what ESPHome's dht, bh1750 and gpio binary_sensor components do, and
// the help page shows the YAML side by side.
//
// What reaches Home Assistant. The network here is SIMULATED and has no MQTT
// broker, so the sketch prints each message it would publish, one line per
// topic, and serves the same values as JSON at /state for the Web tab. Home
// Assistant's RESTful sensor can poll a URL like that on a real network.
#include <BH1750.h>
#include <DHT.h>
#include <WebServer.h>
#include <WiFi.h>
#include <Wire.h>
const int DHT_PIN = 3;
const int PIR_PIN = 18;
const int LED_PIN = 19;
const unsigned long CLIMATE_EVERY_MS = 10000; // a DHT22 wants 2 s at least
const unsigned long LIGHT_EVERY_MS = 5000;
const float LUX_CHANGE = 10.0; // only report light that has really changed
DHT dht(DHT_PIN, DHT22);
BH1750 lightMeter;
WebServer server(80);
float temperature = 0;
float humidity = 0;
float lux = 0;
float luxSent = -1000;
bool motion = false;
unsigned long lastClimate = 0;
unsigned long lastLight = 0;
// One line per message a real device would publish to its MQTT broker.
void publish(const char *topic, const String &payload) {
Serial.print("publish home/multisensor/");
Serial.print(topic);
Serial.print(' ');
Serial.println(payload);
}
void readClimate() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
Serial.println("DHT22: no answer, check the data wire and its pull-up");
return;
}
temperature = t;
humidity = h;
publish("temperature", String(temperature, 1));
publish("humidity", String(humidity, 1));
}
void readLight() {
lux = lightMeter.readLightLevel();
if (fabs(lux - luxSent) >= LUX_CHANGE) {
luxSent = lux;
publish("illuminance", String(lux, 0));
}
}
void sendState() {
String json = "{\"temperature\":";
json += String(temperature, 1);
json += ",\"humidity\":";
json += String(humidity, 1);
json += ",\"illuminance\":";
json += String(lux, 0);
json += ",\"motion\":";
json += motion ? "true" : "false";
json += "}";
server.send(200, "application/json", json);
}
void sendPage() {
String page = "<!doctype html><html><head><meta charset=\"utf-8\">";
page += "<title>Multisensor</title></head><body><h1>Living room</h1><p>";
page += String(temperature, 1);
page += " °C · ";
page += String(humidity, 0);
page += "% · ";
page += String(lux, 0);
page += " lx · motion ";
page += motion ? "detected" : "clear";
page += "</p><p><a href=\"/state\">/state as JSON</a></p></body></html>";
server.send(200, "text/html", page);
}
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
dht.begin();
Wire.begin(4, 5); // SDA, SCL
lightMeter.begin();
Serial.println("Multisensor: simulated WiFi, no MQTT broker in Mokxi.");
WiFi.begin("Mokxi-Lab", "breadboard");
while (WiFi.status() != WL_CONNECTED) {
delay(250);
}
Serial.print("online at http://");
Serial.print(WiFi.localIP());
Serial.println("/state");
server.on("/", sendPage);
server.on("/state", sendState);
server.begin();
delay(2000); // the DHT22 needs a moment after power-up
readClimate();
readLight();
publish("motion", "OFF");
lastClimate = lastLight = millis();
}
void loop() {
server.handleClient();
// Motion is the one reading worth sending the instant it changes.
bool seen = digitalRead(PIR_PIN) == HIGH;
if (seen != motion) {
motion = seen;
digitalWrite(LED_PIN, motion ? HIGH : LOW);
publish("motion", motion ? "ON" : "OFF");
}
unsigned long now = millis();
if (now - lastClimate >= CLIMATE_EVERY_MS) {
lastClimate = now;
readClimate();
}
if (now - lastLight >= LIGHT_EVERY_MS) {
lastLight = now;
readLight();
}
delay(5);
}
Parts list
7 parts, plus the jumper wires. Every one is in the editor's parts bin.
How it is wired
10 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.
- Ground: ESP32-C3-DevKitM-1 pin GND; Light sensor, BH1750 pin GND; Temperature and humidity, DHT22 pin GND
- ESP32-C3-DevKitM-1 pin 3V3; Light sensor, BH1750 pin VCC; Temperature and humidity, DHT22 pin VCC
- ESP32-C3-DevKitM-1 pin 3; Temperature and humidity, DHT22 pin DATA
- ESP32-C3-DevKitM-1 pin 4; Light sensor, BH1750 pin SDA
- ESP32-C3-DevKitM-1 pin 5; Light sensor, BH1750 pin SCL
- ESP32-C3-DevKitM-1 pin 5V; Motion sensor pin VCC
- Ground: ESP32-C3-DevKitM-1 pin GND; Motion sensor pin GND; LED, blue pin C
- ESP32-C3-DevKitM-1 pin 18; Motion sensor pin OUT
- ESP32-C3-DevKitM-1 pin 19; Resistor, 220 Ω pin 1
- Resistor, 220 Ω pin 2; LED, blue pin A
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.