HA | ESPHome | Presence detection with PIR, mmWave Radar and BLE tracking

* Update 17.6.2022 - False positives
* Update 15.6.2022 @ 07:00 - mqtt_room config (with device MAC)
* Update 14.06.2022 @ 08:30 - Added sources
Background
I've wanted to have presence (or occupancy) sensors for most rooms, and I guess this is the dream for many in the Smart Home community to have a reliable presence or occupancy detection per room.
Privacy aside, I have been wanting to have reliable occupancy detection for different rooms as long as I've been doing smart home stuff, ideally also knowing who is in the room to have personalized (conditionalized) automation or actions happen on will (e.g. smart speaker asking in that room do you want to do/hear/activate X, or you press a switch and according to your personalized settings X happens). We are not there yet, lots of obstacles are still in place, but it's getting closer.
PIR
Ideally, I've wanted to have presence (or occupancy) sensors for most rooms. However, this has proven to be somewhat tricky with PIR sensors in rooms where people tend to sit relatively still, like the living room or sometimes even in the kitchen.
mmWave Radar
The millimeter-wave radar seems sensitive enough to detect even the slightest movements, swiping your phone, or even just breathing. On the other hand, the sensor may sense movement even through walls or bounce the signal and detect you even if you are not in the room. Luckily in this sensor, you can set the sensing distance, and it seems to be quite accurate/reliable.
BLE tracking
To make some unique automation for specific persons, I've also wanted to know "who" is in the room, and for that, I've been using BLE tracking and wearable BLE emitting devices (like Oura ring). BLE tracking is still a bit problematic, quite often you cannot reliably track most devices. They tend not to send BLE pings anymore after being paired with another device. For me, Oura has been the most reliable device in this area, although it may take a minute or two to be detected initially. I have heard that not everyone has succeeded with Oura, so I cannot guarantee your success. However, I have tested this with two different Oura rings (Gen 2 and Gen 3 rings), and those I have been able to track even though paired to the Oura App (on Android)
Cameras
This project does not include any cameras, but that might be one way to improve the occupancy and person detection and even detect actions persons are doing and initiate automation to help. BUT for me, this is also a significant privacy concern, even if everything is done locally. Therefore, I have decided to keep cameras out of the house, at least for now while the kids are still living with us so that everyone can have peace of mind inside the house.
Special thanks
I have to thank EverythingSmartHome on making excellent videos and getting information in to peoples attention and showing how to get started on presence detection (among other great stuff he makes).
Parts
- ESP32
- AM312 IR sensor
- DFrobot SEN0395 mmWave Radar (24GHz)
- MicroUSB Power + Cable
- Custom 3D Printed enclosure
- Some wire to connect the parts
- Header rim for the mmWAve Radar
Pinouts / Diagrams





When you use USB to power the ESP32 you get 5V out from the VIN pin, so I used that to power the mmWave Radar sensor.

The Build
Attaching the PIR
Check your pins, you don't want to use any reserved pins.


Making a header for the mmWave Radar sensor





And I also made a two-pin (no contact) support for the opposite side

Attaching the mmWave Radar
Check your pins, you don't want to use any reserved pins. I had to change my pins after the first try. ;) Don't mind the enclosure yet, although it's in the pics, I used my existing hardware from ESPresense.




That's it, the soldering part is done.


Enclosure

I have designed an enclosure that I used for the ESPresense, but I noticed that the same enclosure works for this as well, provided that you soldered the parts somewhat similar to what I did.
With this enclosure, I wanted to keep the height at a minimum (reason the headers need to be removed), be able to mount it on walls, etc, and be able to slightly orient it while mounted.
There is a selection of slightly different mounts and enclosure versions with and without vents, that you can combine.

Assembling the enclosure
You need 3 standard computer screws (UTS-6-32), these are for the PIR mount and for the adjustment ball.

Mounting the PIR
Take off the plastic "hat" and the plastic "collar", to be able to slide the PIR mount through.




Attach the mounting ball
You can place the mounting ball in 3 different places, this can give you slightly different options to orient the enclosure after mounting to the wall piece, so just choose the position that suits your needs.




Add the ESP32 into the case


Wall mount
Screw the wall piece into a suitable place and tighten the enclosure into the wall piece with the plastic screw.


ESPHome Configuration
This part you may want to do with Chrome web browser as it gives you an easier initial installation through a USB cable.
Not going through ESPHome installation in this guide, but I have it installed as an Add-on in Home Assistant.
Install ESPhome into your ESP32 device

Open the WEB UI and click + New Device




Configure your device(s)
Sources:
- Everything Smart Home - I Built My Own Presence Detection Sensor!
- https://community.home-assistant.io/t/low-latency-presence-sensor-mmwave-pir-using-esphome/407553


You may want to create secrets in ESPHome for your passwords
esphome:
name: kitchen-presence
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
# Over The Air updates
ota:
password: !secret ota_password
# Connection to WiFi
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "kitchen-Presence"
password: !secret ap_password
# MQTT client component
mqtt:
broker: !secret mqtt_ip
username: !secret mqtt_username
password: !secret mqtt_password
discovery: False # Only if you use the HA API usually
id: mqtt_client
substitutions:
room_name: bedroom
globals:
- id: room_topic
type: std::string
initial_value: '"room_presence/${room_name}"'
# Enable BLE tracker
esp32_ble_tracker:
on_ble_advertise:
- then:
- mqtt.publish_json:
topic: 'room_presence/${room_name}'
payload: |-
root["id"]=x.address_str();
root["name"]=x.get_name();
root["rssi"]=x.get_rssi();
# adjusted TX Power for Oura
root["distance"]=pow(10, (float)(-81 - x.get_rssi()) / (10 * 2));
# fallback if WiFi fails
captive_portal:
web_server:
port: 80
version: 2
include_internal: true
auth:
username: !secret web_user
password: !secret web_pass
# Enable HTTP/HTTPS requests
http_request:
# default ESPHome
useragent: esphome/$device_name
# request time out / default 5s
timeout: 2s
# UART communication settings for mmWave sensor | Remember to set pins
uart:
id: uart_bus
# pin to send data from ESP
tx_pin: GPIO25
# pin to receive data to ESP
rx_pin: GPIO33
# baud rate for UART bus (required)
baud_rate: 115200
debug:
# debug communication to both directions
direction: BOTH
# useful to debug all incoming communication
dummy_receiver: true
after:
delimiter: "\n"
sequence:
- lambda: UARTDebug::log_string(direction, bytes);
switch:
# Switch for Safe Mode
- platform: safe_mode
internal: true
name: use_safe_mode
binary_sensor:
# mmWave Sensor | remember to set the pin
- platform: gpio
name: kitchen_mmwave
pin:
number: GPIO27
mode: INPUT_PULLDOWN
# PIR Sensor | remember to set the pin
- platform: gpio
name: kitchen_pir
id: kitchen_pir
pin:
number: GPIO18
mode: INPUT_PULLDOWN
# delay detection off by 3s
filters:
- delayed_off: 3s
# Jukka Oura Gen3 BLE sensor
- platform: ble_presence
mac_address: 'xx:xx:xx:xx:xx:xx'
name: "Jukka Oura Kitchen"
# Piia Oura Gen2 BLE sensor
- platform: ble_presence
mac_address: 'xx:xx:xx:xx:xx:xx'
name: "Piia Oura"
# Anton FitBit BLE sensor
- platform: ble_presence
mac_address: 'xx:xx:xx:xx:xx:xx'
name: "Anton FitBit"
sensor:
# Jukka Oura Gen3 RSSI sensor value
- platform: ble_rssi
mac_address: 'xx:xx:xx:xx:xx:xx'
name: "Jukka Oura RSSI kitchen"
# Piia Oura Gen2 RSSI sensor value
- platform: ble_rssi
mac_address: 'xx:xx:xx:xx:xx:xx'
name: "Piia Oura RSSI kitchen"
# Anton FitBit RSSI sensor value
- platform: ble_rssi
mac_address: 'xx:xx:xx:xx:xx:xx'
name: "Anton FitBit RSSI kitchen"
number:
## SENSING DISTANCE value for mmWave Radar
- platform: template
# name for the number (required)
name: kitchen_distance
# ID for code generation (optional)
id: kitchen_distance
# Min & Max values for the distance slider (required)
min_value: 0 # Default, if initial_value is not set
max_value: 1350
# default number (sensing distance) value in cm
initial_value: 315
# When TRUE any command sent to the template number will immediately update the reported state.
optimistic: true
# Slider changes the number (sensing distance) value in steps of 15cm
step: 15
# Saves and loads the state to RTC/Flash
restore_value: true
# Unit
unit_of_measurement: cm
# Action to be performed when requested from frontend
set_action:
# STOP the sensor first
- uart.write: "sensorStop"
# Wait for 1 sec for the sensor to stop
- delay: 1s
# Write the new sensing distance value to the sensor detRangeCfg
- uart.write: !lambda
int cm = (int)ceil(x / 15.0);
std::string cms = "detRangeCfg -1 0 " + to_string(cm);
return std::vector<unsigned char>(cms.begin(), cms.end());
# Wait for 1 sec for the write
- delay: 1s
# When parameters are reconfigured through UART and not saved, this command (Fixed values) saves the new configuration parameters to the sensor Flash
# saveCfg par1 par2 par3 par4
- uart.write: "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"
# Wait for 1 sec for the write
- delay: 1s
# START the sensor again
- uart.write: "sensorStart"
## LATENCY FOR TURNING mmWave OFF when no movement detected.
- platform: template
name: kitchen_latency
id: kitchen_latency
# Min & Max values for the latency slider (required) in ms
min_value: 0
max_value: 65000
# default number (latency) value in ms
initial_value: 12500
# When TRUE any command sent to the template number will immediately update the reported state.
optimistic: true
step: 25
# Saves and loads the state to RTC/Flash
restore_value: true
unit_of_measurement: ms
set_action:
# STOP the sensor first
- uart.write: "sensorStop"
- delay: 1s
- uart.write: !lambda
int ms = (int)ceil(x / 25.0);
std::string mss = "outputLatency -1 0 " + to_string(ms);
return std::vector<unsigned char>(mss.begin(), mss.end());
- delay: 1s
# When parameters are reconfigured through UART and not saved, this command (Fixed values) saves the new configuration parameters to the sensor Flash
# saveCfg par1 par2 par3 par4
- uart.write: "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"
- delay: 1s
# START the sensor again
- uart.write: "sensorStart"
button:
# Restart the device
- platform: restart
name: restart_kitchen_radar
# Reset mmWave sensor configuration parameters to Factory defaults
- platform: template
name: "factory_reset_kitchen_dfrobot"
id: "factory_reset_kitchen_dfrobot"
on_press:
# STOP the sensor first
- uart.write: "sensorStop"
- delay: 1s
# reset command (fixed values)
- uart.write: "factoryReset 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"
- delay: 3s
# START the sensor again
- uart.write: "sensorStart"
Configuring LED brightness
This part was highly technical and required resources and tools beyond mine.
Borrowed black nail polish from my daughter

Borrowed a needle from my wife, and used the needle to apply 3 layers of the black stuff over the LED on ESP32 and on the mmWave Radar Sensor

TO DO
- Combining the PIR and mmWave Radar into a single "occupancy sensor"
It's probably possible to combine the PIR and mmWave into a single sensor inside ESPHome config .. but I haven't tried it yet, or could be done in HA, or in Node-RED as well, haven't figured out yet what would be the best option.
- The BLE devices to mqtt_room
Setup MQTT broker conf in ESPHome and on_ble advertise, mqtt_room
- Setup dashboard for the devices

False Positives
Someone mentioned that they have had false-positive detections due to the WiFi. I'll keep actively monitoring this for few days.
Update 17.6.2022
Looks like there has been at least one false positive with the PIR sensor

