Radar hallway sensor on an ESP32-C3
An RCWL-0516 radar holds a hallway "occupied" for ten seconds after the last movement and lights the lamp only when it is dark, with the honest limits of a Doppler sensor. The whole build, an ESP32-C3 and 4 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 radar sensor: a hallway light that waits for the dark.
//
// RCWL-0516 on GPIO 18 microwave Doppler radar, OUT high while it sees movement
// light module on GPIO 0 the analog output of an LDR board, darker reads higher
// LED on GPIO 19 the hallway light, standing in for a relay or a smart bulb
//
// Honest first: the RCWL-0516 is not an mmWave presence sensor. It is a 3.2 GHz
// Doppler board that sees MOVEMENT, like a PIR that also sees through plastic
// and thin walls. Sit perfectly still and it lets go. The 24 GHz mmWave boards
// people use for true presence, such as the LD2410, also sense the tiny motion
// of breathing and talk over UART; Mokxi does not model them. What this sketch
// shows is the trick everybody uses with a motion sensor anyway: an off delay,
// so a short pause does not turn the lights out (ESPHome calls it delayed_off).
//
// The light logic runs on the device, so the hallway still works when Home
// Assistant is down. Mokxi has no MQTT broker: the sketch prints what it would
// publish, and serves its state as JSON at /state for the Web tab.
#include <WebServer.h>
#include <WiFi.h>
const int RADAR_PIN = 18;
const int LIGHT_SENSOR_PIN = 0;
const int LAMP_PIN = 19;
const unsigned long OFF_DELAY_MS = 10000; // keep "occupied" this long after the last movement
const int DARK_ABOVE = 3000; // raw reading, 0 to 4095: higher is darker
WebServer server(80);
bool moving = false;
bool occupied = false;
bool lampOn = false;
bool lastDark = false;
unsigned long lastMovement = 0;
void publish(const char *topic, const char *payload) {
Serial.print("publish home/hallway/");
Serial.print(topic);
Serial.print(' ');
Serial.println(payload);
}
bool isDark() {
// A little hysteresis, so dusk does not flicker the light on and off.
int raw = analogRead(LIGHT_SENSOR_PIN);
if (lastDark) lastDark = raw > DARK_ABOVE - 150;
else lastDark = raw > DARK_ABOVE;
return lastDark;
}
void setLamp(bool on) {
if (on == lampOn) return;
lampOn = on;
digitalWrite(LAMP_PIN, on ? HIGH : LOW);
publish("light", on ? "ON" : "OFF");
}
void sendState() {
String json = "{\"motion\":";
json += moving ? "true" : "false";
json += ",\"occupancy\":";
json += occupied ? "true" : "false";
json += ",\"light\":";
json += lampOn ? "true" : "false";
json += ",\"light_level\":";
json += String(analogRead(LIGHT_SENSOR_PIN));
json += "}";
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
pinMode(RADAR_PIN, INPUT);
pinMode(LAMP_PIN, OUTPUT);
digitalWrite(LAMP_PIN, LOW);
Serial.println("Radar hallway sensor: 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("/state", sendState);
server.on("/", sendState);
server.begin();
publish("occupancy", "OFF");
}
void loop() {
server.handleClient();
unsigned long now = millis();
bool seen = digitalRead(RADAR_PIN) == HIGH;
if (seen != moving) {
moving = seen;
publish("motion", moving ? "ON" : "OFF");
}
if (moving) lastMovement = now;
// Occupied the moment anything moves, clear only after a quiet OFF_DELAY_MS.
if (moving && !occupied) {
occupied = true;
publish("occupancy", "ON");
} else if (!moving && occupied && now - lastMovement >= OFF_DELAY_MS) {
occupied = false;
publish("occupancy", "OFF");
setLamp(false);
}
// While someone is about, the light comes on as soon as it is dark enough.
if (occupied && !lampOn && isDark()) setLamp(true);
delay(10);
}
Parts list
6 parts, plus the jumper wires. Every one is in the editor's parts bin.
How it is wired
8 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; Photoresistor sensor module pin GND
- ESP32-C3-DevKitM-1 pin 3V3; Photoresistor sensor module pin VCC
- ESP32-C3-DevKitM-1 pin 0; Photoresistor sensor module pin AO
- ESP32-C3-DevKitM-1 pin 5V; Microwave motion sensor pin VIN
- Ground: ESP32-C3-DevKitM-1 pin GND; Microwave motion sensor pin GND; LED, yellow pin C
- ESP32-C3-DevKitM-1 pin 18; Microwave motion sensor pin OUT
- ESP32-C3-DevKitM-1 pin 19; Resistor, 220 Ω pin 1
- Resistor, 220 Ω pin 2; LED, yellow 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.