Building a Smart Light Automation with C4002 mmWave Radar and ESPHome in Home Assistant
Previous article: From PIR to mmWave: My Smart Lighting Automation in Home Assistant
For more information on the principles of automated lighting systems, please read: Behind the Light Automation: Understanding Triggers, Conditions, and Actions in Home Assistant
1. Introduction
This tutorial is a step-by-step guide to building a presence-sensing light automation system using FireBeetle 2 ESP32-C6, the C4002 mmWave radar sensor, and Home Assistant. The system automatically turns on the lights when someone is present and turns them off after the area is vacant.
Throughout this guide, you’ll learn the complete process, including ESPHome configuration, MQTT relay integration, and writing Home Assistant automation rules, enabling you to create a reliable and responsive light automation setup.
2. Software and Hardware Preparation
2.1 Software Preparation
- Home Assistant host (Home Assistant OS / Supervised / Container versions are all supported)
- Arduino IDE
2.2 Hardware Preparation
- FireBeetle 2 ESP32-C6 IoT Development Board x 2
- Fermion: C4002 mmWave Human Presence Sensor x 1
- Gravity: Easy Relay Module x 1
- USB LED x 1
- Jumper Wires 9" F/F x 1
- USB 3.0 to Type-C Cable x 2
- 4×AA Battery Holder x 1
- AA Batteries x 4
- DC Barrel Jack Adapter - Female x1
3. Step One: Connecting the C4002 for Human Presence Detection
3.1 Wiring Diagram of ESP32-C6 and C4002
Refer to the wiring diagram below to complete the connection between the ESP32-C6 and the C4002 mmWave radar sensor.For detailed wiring instructions, please refer to the C4002 Integration with Home Assistant Tutorial

3.2 C4002 Integration with ESPHome
Log in to the Home Assistant system. Click on 'ESPHome Builder' in the left sidebar to enter the ESPHome page.

On the ESPHome page, click + NEW DEVICE.

Next, on the device creation wizard page, simply click CONTINUE.

Enter a custom device name (e.g., Presence Sensor), then click NEXT.

On the hardware model selection page, choose ESP32‑C6 for the FireBeetle 2 ESP32‑C6 you are using. When the page shows Configuration created, click SKIP.

After the device is successfully created, go to the ESPHome device management list and locate the newly created presence-sensor device.

Click EDIT — this will open the device’s YAML configuration file, as shown below.

Paste the following configuration code for the FireBeetle 2 ESP32-C6 and the C4002 sensor at the end of the YAML file.
# UART Configuration
uart:
id: uart_bus
tx_pin: GPIO5
rx_pin: GPIO4
baud_rate: 115200
# External components
external_components:
- source:
type: git
url: https://github.com/cdjq/esphome.git
ref: dev
components:
- dfrobot_c4002
refresh: 0s
# C4002 Component Configuration
dfrobot_c4002:
id: my_c4002
uart_id: uart_bus # Specify UART bus explicitly
# Sensor Configuration Section
sensor:
# C4002 Sensors
- platform: dfrobot_c4002
c4002_id: my_c4002
movement_distance:
name: "Motion Distance"
id: movement_distance_sensor
unit_of_measurement: "m"
accuracy_decimals: 2
icon: "mdi:ruler"
device_class: "distance"
state_class: "measurement"
existing_distance:
name: "Presence Distance"
id: existing_distance_sensor
unit_of_measurement: "m"
accuracy_decimals: 2
icon: "mdi:account"
device_class: "distance"
state_class: "measurement"
movement_speed:
name: "Motion Speed"
id: movement_speed_sensor
unit_of_measurement: "m/s"
accuracy_decimals: 2
icon: "mdi:speedometer"
device_class: "speed"
state_class: "measurement"
movement_direction:
name: "Motion Direction"
id: movement_direction_sensor
icon: "mdi:compass"
# internal: true # Comment out temporarily for data testing
target_status:
name: "Target Status"
id: target_status_sensor
icon: "mdi:target"
# internal: true # Comment out temporarily for data testing
# WiFi Signal Sensor
- platform: wifi_signal
name: "WiFi Signal Strength"
update_interval: 30s
unit_of_measurement: "dBm"
accuracy_decimals: 0
device_class: "signal_strength"
entity_category: "diagnostic"
text_sensor:
- platform: template
name: "Movement Direction Text"
id: movement_direction_text
icon: "mdi:directions"
lambda: |-
if (id(movement_direction_sensor).has_state()) {
int d = id(movement_direction_sensor).state;
if (d == 0) return {"Approaching"};
else if (d == 1) return {"No Direction"};
else if (d == 2) return {"Away"};
else return {"Unknown"};
}
return {"No Data"};
update_interval: 1s
- platform: template
name: "Target Status Text"
id: target_status_text
icon: "mdi:human-greeting"
lambda: |-
if (id(target_status_sensor).has_state()) {
int d = id(target_status_sensor).state;
if (d == 0) return {"No Target"};
else if (d == 1) return {"Static Presence"};
else if (d == 2) return {"Motion"};
else return {"Unknown"};
}
return {"No Data"};
update_interval: 1s
- platform: dfrobot_c4002
c4002_id: my_c4002
c4002_text_sensor:
name: "C4002 Log"
icon: "mdi:message-text-outline"
# Switch Configuration Section
switch:
- platform: dfrobot_c4002
c4002_id: my_c4002
switch_out_led:
name: "Out LED Switch"
icon: "mdi:led-on"
switch_run_led:
name: "Run LED Switch"
icon: "mdi:led-on"
switch_factory_reset:
name: "Factory Reset"
icon: "mdi:restart"
entity_category: "config"
switch_environmental_calibration:
name: "Sensor Calibration"
icon: "mdi:calibration"
entity_category: "config"
# Select Configuration Section - Fixed operating_mode
select:
- platform: dfrobot_c4002
c4002_id: my_c4002
operating_mode:
name: "OUT Mode"
icon: "mdi:account"
entity_category: "config"
options:
- "Mode_1"
- "Mode_2"
- "Mode_3"
# Number Configuration Section
number:
- platform: dfrobot_c4002
c4002_id: my_c4002
max_range:
name: "Max Detection Distance"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
min_range:
name: "Min Detection Distance"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
light_threshold:
name: "Light Threshold"
unit_of_measurement: "lx"
icon: "mdi:lightbulb"
entity_category: "config"
area1_min:
name: "Area 1 Min"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
area1_max:
name: "Area 1 Max"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
area2_min:
name: "Area 2 Min"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
area2_max:
name: "Area 2 Max"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
area3_min:
name: "Area 3 Min"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
area3_max:
name: "Area 3 Max"
unit_of_measurement: "m"
icon: "mdi:ruler"
entity_category: "config"
target_disappeard_delay_time:
name: "Target Disappear Delay"
unit_of_measurement: "s"
icon: "mdi:timer"
entity_category: "config"
Click INSTALL to start compiling the firmware.

A window will pop up to select the installation method. Choose Plug into this computer, then click Download project to start compiling the firmware.

After the firmware compilation is complete, click Download project, select Factory format in the flashing options, and download the firmware file (named presence-sensor.factory.bin).

Once the firmware download is complete, click Open ESPHome Web.

Make sure the FireBeetle 2 ESP32‑C6 is connected to your computer via a USB cable.

Then, on the flashing tool page, click CONNECT.

In the pop-up serial port selection box, choose the port corresponding to the FireBeetle 2 ESP32‑C6, then click Connect to complete the device connection.

Once the ESP32‑C6 is successfully connected to the flashing tool, click INSTALL on the page.

Click Choose File, select the previously downloaded presence-sensor.factory.bin firmware file, then click INSTALL to start flashing.

Wait for the firmware flashing to complete; the page will indicate when the process is successful.

Unplug the USB cable to disconnect the ESP32‑C6 from your computer. Then, following the wiring diagram below, connect the battery pack, ESP32‑C6, and C4002 to assemble the presence sensor module.

Return to the Home Assistant page, and you should see the presence sensor device online.

On the Home Assistant Overview page, click the three-dot icon in the top right corner and select Edit Dashboard.

Then, click + ADD CARD.

To display data from the C4002 sensor using a card, select an Entities card, then choose the entities related to the C4002 (e.g., Motion Distance).

You can customize the card name and icon, then click Save. The system will create a dedicated Entities card to view real-time data from the C4002 presence detection module, such as detected human motion.

Click Save, and you will see the newly added Entities card on the dashboard, displaying real-time data from the C4002 presence detection module, such as motion speed, distance, and direction.

In addition, you can choose different card types to achieve diverse data visualization effects.

It can display data such as the target status of the presence sensor, presence detection, motion detection, and detection range settings, as shown in the figure below.

4. Step Two: Connecting the Relay to Control the Lights
4.1.1 Wiring Diagram for ESP32-C6 and Relay
Refer to the wiring diagram below to complete the wiring between the ESP32-C6, the relay, and the USB light.

4.1.2 Relay Integration into Home Assistant via MQTT
Home Assistant has a built-in MQTT integration. We can establish stable communication between Home Assistant and the ESP32-C6 through MQTT to achieve state synchronization and remote control of the relay (and the USB light), as shown in the figure below.

To establish communication between Home Assistant, the ESP32-C6, and the relay via MQTT, we first need to create an MQTT Broker responsible for sending and receiving messages. Here we use the official add-on in Home Assistant: Mosquitto Broker.
In Home Assistant, open Settings and go to Add-ons.

Then click Add-on store in the bottom-left corner, search for and install Mosquitto broker.

In Home Assistant, open Settings, go to Add-ons, then search for and install Mosquitto broker.
After installation, start the service. It is recommended to enable Start on boot and Watchdog to ensure stable system operation.

To ensure communication security, we need to create a login account for MQTT.
In Home Assistant, go to Settings, select People, and add a new user with a username and password.
This username and password will later be used in the ESP32 program to connect to the MQTT server.

We can write MQTT client code in the Arduino IDE to implement WiFi connection, MQTT communication, and relay driving for the ESP32-C6.
Note: The ESP32-C6 and the Home Assistant host must be connected to the same WiFi network to ensure they are on the same local area network (LAN).
Next, we will write the code for the ESP32-C6 to connect to the network and establish communication via MQTT. Open the Arduino IDE.

Copy and paste the following code into the file.
#include <WiFi.h>
#include <PubSubClient.h>
/* ================== WiFi Configuration ==================
Replace with your own WiFi SSID and password
*/
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
/* ================== MQTT Configuration ==================
Replace with your MQTT broker IP address
Replace with your MQTT username and password
*/
const char* mqtt_server = "YOUR_MQTT_BROKER_IP";
const char* mqtt_user = "YOUR_MQTT_USERNAME";
const char* mqtt_pass = "YOUR_MQTT_PASSWORD";
/* ================== MQTT Topics ==================
You may change the topic names according to your project
*/
#define TOPIC_STATUS "c4002/status"
#define TOPIC_RELAY_SET "c4002/relay/set"
#define TOPIC_RELAY_STATE "c4002/relay/state"
/* ================== Relay Configuration ==================
Adjust the GPIO pin according to your hardware wiring
*/
#define RELAY_PIN 14
#define RELAY_ON LOW
#define RELAY_OFF HIGH
/* ================== MQTT Client ================== */
WiFiClient espClient;
PubSubClient client(espClient);
/* =================================================
MQTT Message Callback Function
This function is triggered when a subscribed topic
receives a message.
================================================= */
void callback(char* topic, byte* payload, unsigned int length) {
char msg[20];
memcpy(msg, payload, length);
msg[length] = '\0';
Serial.print("MQTT Received: ");
Serial.println(msg);
if (strcmp(topic, TOPIC_RELAY_SET) == 0) {
if (strcmp(msg, "ON") == 0) {
digitalWrite(RELAY_PIN, RELAY_ON);
client.publish(TOPIC_RELAY_STATE, "ON", true);
Serial.println("Relay turned ON");
}
else if (strcmp(msg, "OFF") == 0) {
digitalWrite(RELAY_PIN, RELAY_OFF);
client.publish(TOPIC_RELAY_STATE, "OFF", true);
Serial.println("Relay turned OFF");
}
}
}
/* =================================================
Connect to WiFi Network
================================================= */
void setup_wifi() {
Serial.print("Connecting to WiFi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
}
/* =================================================
Reconnect to MQTT Broker
================================================= */
void reconnect() {
while (!client.connected()) {
Serial.print("Connecting to MQTT...");
if (client.connect("esp32_relay_client",
mqtt_user,
mqtt_pass,
TOPIC_STATUS,
0,
true,
"offline")) {
Serial.println("MQTT Connected");
// Publish online status (retained message)
client.publish(TOPIC_STATUS, "online", true);
// Subscribe to relay control topic
client.subscribe(TOPIC_RELAY_SET);
// Initialize relay state to OFF
digitalWrite(RELAY_PIN, RELAY_OFF);
client.publish(TOPIC_RELAY_STATE, "OFF", true);
}
else {
Serial.println("MQTT connection failed, retrying in 2 seconds...");
delay(2000);
}
}
}
/* =================================================
Setup Function (runs once at boot)
================================================= */
void setup() {
Serial.begin(115200);
delay(2000);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, RELAY_OFF);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
Serial.println("Relay system initialization completed");
}
/* =================================================
Main Loop
================================================= */
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}
Due to differences in each user's network environment and Home Assistant configuration, several items in the code must be modified according to actual conditions.
Please replace "YOUR_WIFI_SSID" with the name of the WiFi network you are currently using, and replace "YOUR_WIFI_PASSWORD" with the corresponding WiFi password. It is mandatory to fill in the same WiFi network as the Home Assistant host to ensure the ESP32-C6 and Home Assistant are on the same local area network (LAN).
Please replace "mqtt_server" with the IP address of the Home Assistant host running the Mosquitto Broker. You can check the IPv4 address of the current device in Home Assistant under Settings → System → Network.
After modifying the WiFi and MQTT-related information in the code, you can flash the program to the ESP32-C6 development board. First, click Tools in the top menu of the Arduino IDE, find the Board option, and select DFRobot FireBeetle 2 ESP32-C6. Then go to Tools → Port and select the corresponding serial port device.
Copy and paste the following code into the file.

After confirming that the development board model and serial port are selected correctly, click the Upload button in the top-left corner of the Arduino IDE. The program will then start compiling and flashing to the ESP32-C6. Do not disconnect the USB connection during the flashing process.
Once the program is uploaded successfully, open the Home Assistant page and create an MQTT switch entity. The most common way is to add the configuration by editing the configuration.yaml file. Click File editor, locate the configuration.yaml file, and double-click to open it.

After confirming that the development board model and serial port are selected correctly, click the Upload button in the top-left corner of the Arduino IDE. The program will then start compiling and flashing to the ESP32-C6. Do not disconnect the USB connection during the flashing process.
switch:
- name: "C4002 Relay"
unique_id: c4002_relay
command_topic: "c4002/relay/set"
state_topic: "c4002/relay/ state"
payload_on: "ON"
payload_off: "OFF"
retain: trueAfter pasting, see the image below.

After saving the file, click Settings, find System, click the button in the top right corner, select Restart Home Assistant, and restart Home Assistant to apply the configuration.

After completing Home Assistant setup, click Settings, then find Devices & services. You will see an entity appear under MQTT. Click on MQTT.

You can now see the newly created relay switch entity under the MQTT integration. Click MQTT to view detailed information about this entity, including its current state and control topics.

Return to the Overview page. Click the Edit Dashboard button in the top-right corner.

Then click + ADD CARD to add a card.

Select the Switch card.

Once you enter the card settings interface, find the previously created ESP32 Relay switch entity in the Entity dropdown menu, set its name to light, then click Save to confirm.

The completed setup page is shown below.

Now you can control the light on and off in the Home Assistant system using the light switch on the dashboard.
When you click the switch on the Home Assistant dashboard to turn it to the ON state, the system sends an on command to the ESP32-C6 via MQTT.After receiving the message, the ESP32-C6 controls the relay to close, thereby turning on the connected light.At the same time, the ESP32-C6 publishes the current state back to Home Assistant via MQTT, and the switch state on the dashboard immediately updates to On.

When you click the switch on the dashboard to set it to the OFF state, Home Assistant will send a turn-off command to the ESP32-C6 via MQTT. After receiving the message, the ESP32-C6 will disconnect the relay circuit, thereby turning off the light.

5. Step Three: Writing Automation Rules in Home Assistant
"When the room is occupied, the lights turn on; when the room is unoccupied, the lights turn off." Therefore, the trigger for the automatic lighting automation should be "room occupancy status". The entity sensor.esp32_c6_target_status_text – which is output by the C4002 millimeter-wave radar sensor in Home Assistant for target detection – returns three states: unoccupied, person stationary, and person moving. For ease of use in automations, we need to convert it into a binary sensor that only indicates two states: occupied or unoccupied.
On the Home Assistant interface, click 'File editor', locate the configuration.yaml file, and double-click to open it.

Add the following content at the end of the configuration.yaml file to create a binary sensor. When the sensor state is Motion or Static Presence, the binary sensor will show on (occupied); otherwise, it will show off (unoccupied).
template:
- binary_sensor:
- name: "Room Occupancy"
unique_id: room_occupancy_binary
device_class: occupancy
state: >
{{ is_state('sensor.esp32_c6_target_status_text', 'Motion')
or is_state('sensor.esp32_c6_target_status_text', 'Static Presence') }}After saving the file, click 'Settings', find 'System', click the button in the top right corner, select 'Restart Home Assistant', restart Home Assistant to make the configuration take effect.

After the restart is complete, click 'Settings' and find 'Developer tools'.

Click the States tab, enter binary_sensor.room_occupancy in the Entity search box. You can find the occupancy binary sensor we created and its status: 'on' means occupied, 'off' means unoccupied.

Open the dashboard → click Edit Dashboard in the top-right corner.

Click + Add Card, select Entities Card, then choose the entity named Room Occupancy. You can intuitively see that the motion or static status detected by the C4002 has been converted into the binary sensor status.

Next, we will create the automatic light on/off rule. Open the Home Assistant left menu → Settings, click Automations & Scenes → Automations.

Click the + Add Automation in the bottom right corner and select Start with an empty automation.

Click 'ADD trigger', select 'Entity', then select 'State'.Click 'ADD trigger', select 'Entity', then select 'State'.

On the right side, select Room Occupancy for the Entity field. Set To (state) to Detected. Set For (duration) to 00:01:00 (1 minute). This means the automation will be triggered when the room remains in the "Detected (occupied)" state for 1 minute. Finally, set the trigger name to 'when room occupancy changes to detected for 1m'.

After setting up the trigger, we need to add a condition to prevent false triggering.
The C4002 radar has a wide detection range and may detect people outside the room, causing the light to turn on unnecessarily. To improve accuracy, we add a distance limit: only activate when the target is less than 2 meters away (inside the room).
Now configure the Condition:
Click Add condition and select Template.

Enter the following code and click Save.
{% set p = states('sensor.esp32_c6_motion_distance') | float(99) %}
{% set m = states('sensor.esp32_c6_presence_distance') | float(99) %}
{{ p < 2 or m < 2 }}
In this code, we first read the values of two distance sensors: p represents the distance to a moving target, and m represents the distance to a stationary presence. By using float(99) as a fallback, if the sensor temporarily has no data or returns an abnormal value, it will default to 99. This prevents automation errors or false triggers caused by missing data. The final judgment logic is: the condition is met if either the moving distance or stationary distance is less than 2 meters.

In the automation editor, click Add Action, select Switch as the action type, then choose Turn on as the action.

In the switch list, select the relay entity used to control the light: C4002 Relay (corresponding to switch.c4002_relay).
When the automation is triggered and passes the condition check, Home Assistant will send an ON command to the relay, closing the circuit and turning on the light.

After configuring the automatic light-on rule, we need to set up an "auto light-off on prolonged vacancy" rule. This ensures the system automatically turns off the light when the room is unoccupied for a set period, balancing energy efficiency with smart control.
Continue by clicking + Add Automation in the bottom-right corner and select: Start with an empty automation.
Click ADD Trigger, then set the Trigger type: select Entity, then State.Set Entity to Room Occupancy.Set To (state) to Clear.Set For (duration) to 00:01:00 (1 minute).

This setting means: the automation is triggered when the Room Occupancy state changes to Clear (unoccupied) and remains so for 1 minute.
The prerequisite for auto light-off should be: if the room is unoccupied for 1 minute and the light is on, the light-off action is executed. Therefore, set the condition to the light being in the "on" state.
Click Add Condition, select Entity, then click State.

Set Entity to C4002 Relay and State to On. Click Save.

Set up the light-off action the same way:
click Add Action, select Switch, choose Turn off as the action, and select the entity C4002 Relay.

After configuring the automatic light-on and light-off rules, we enter the stage of system verification and tracing to ensure the entire automation loop works as expected.
You can click Trace to track the automatic execution process of events.

First, you can intuitively check the status changes of the Room Occupancy binary sensor on the Home Assistant dashboard.
When someone enters the room, the status changes from off (unoccupied) to on (occupied).
After a 1-minute delay, the relay closes automatically and the light turns on.

When the room has been unoccupied for one minute and the light is on, the relay turns off and the light switches off automatically.

This series consists of three articles on Home Assistant Smart Lighting Automation System,Each article focuses on a different aspect to give you a complete understanding of Lighting Automation System:
This series consists of three articles on Home Assistant Smart Lighting Automation System. Each article focuses on a different aspect to give you a complete understanding of Lighting Automation System:
- Project Showcase And Experience: From PIR to mmWave: My Smart Lighting Automation in Home Assistant
- Hands-On Tutorial: Building a Smart Light Automation with C4002 mmWave Radar and ESPHome in Home Assistant
- Home Assistant Automation Explained










