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

HA | ESPHome | Presence detection with PIR, mmWave Radar and BLE tracking
The Presence Unit in the Kitchen

📢
* Update 06.09.2022 - Corrected the UART pins in the code
* 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

ℹ️
If you want to use the 3D printable enclosure, you need to remove headers from the ESP32 and from the PIR. I wanted to keep the enclosure height as minimal as I could.
ℹ️
This project is powered through USB, to use the ESP32 VIN as a 5V out for the mmWave Radar Sensor

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.

ESP32 and the AM312 IR sensor - Headers removed (soldered off)
Soldered the PIR sensor to ESP32

Making a header for the mmWave Radar sensor

Header
two 4 pin pieces
Took off the pins from the second piece to make it a "riser"
Sliding the "riser" in
The raised header is needed so that there is a small gap between the ESP32 and the mmWave 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.

Soldering the 4 pin header to the mmWave Radar sensor and ~2cm wires for 5V and Ground.
Soldered the 5V and Ground wires to ESP32
Soldered the 2-pin support into ESP32
Used helpers to keep the sensor in place, while soldering it to the ESP32 (ideally you want to keep it somewhat straight .. yea, I have an OCD for these kinds of things)

That's it, the soldering part is done.

Enclosure

3D design ESP32 - PIR - Case with wall Mount | Tinkercad
3D design ESP32 - PIR - Case with wall Mount created by evishome with Tinkercad

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.

Take off the plastic "hat" and the "collar" and slide the mounting piece through, notice the orientation!
Place the "collar" (notice the metal piece for orientation) and the "hat" back
Screw the mount into the enclosure cover, the counter pieces are threaded, so depending on your print accuracy they should go in quite easily. Don't overtighten!

Attach the mounting ball

⚠️
Before screwing the mounting ball into the enclosure, remember to add the plastic screw in between the ball and the enclosure.

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.

Don't forget to add the plastic screw!
Select the position for the mounting ball and screw it in place.

Add the ESP32 into the case

Check the PIR wires don't get squashed
Snap the case cover in place

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

Attach the ESP32 with a USB cable to your computer and click Connect
Click Connect
Wait for the installation to complete

Configure your device(s)

Sources:

mmWave Radar Sensor Arduino-Human Presence Detection Wiki - DFRobot
Wiki: The millimeter-wave radar can sense human presence, stationary and moving people within the detection area. Moreover, it can even detect static or stationary human presence such as a sleeping person.
ESPHome — ESPHome
ESPHome Homepage - Reimagining DIY Home Automation. ESPHome is a framework that tries to provide the best possible use experience for using ESP8266 and ESP32 microcontrollers for Home Automation. Just write a simple YAML configuration file and get your own customized firmware.
⚠️
If you have multiple devices, remember to change the names and IDs
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

I heard this might be "MacGyver Approved"

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

mmwave looks clean during the night
PIR has one detection around 3 am