mmWave Presence Detection - ESPHome style

I was thinking maybe there was noise making it into the circuit causing SNR to drop.

The main issue is that it isn’t really useful for presence detection at single-digit SNRs outside of a lab setting. Sitting instead of standing will drop it under 1, whereas sitting still drops it to 0. If a blanket is then added into the mix it drops out with even larger motions like the head turning or shifting around.

Even more annoying is that it still gets false positives every night while we are sleeping in another room. It will turn on for 5 seconds, and then off for an hour or so and repeats for 2-3 hours where it then acts fine for the rest of the day.

Unclear if there is any way to confirm or remedy this suspicion.

Sitting implies a smaller target. Which means you either need to be at best, better aimed to the sitting location. Or at worst, closer to the sensor.

I can confirm that the HS2643A variant is better at distance but have yet to quantify “better” out side of, “the DFRobot/HS2A43A was on the cusp of detection at the needed distance (4M) and the HS2643A solved that specific use-case.”

This is pretty much the hardest use-case; you have obscured most of the target which minimized RCS and therefore drops the SNR. The only solution here is move the sensor closer.

While the above challenges suggest the sensor isn’t “sensitive enough”, this last challenge demonstrates that it is plenty sensitive to “any movement it can see.” Which is what you need to determine, where that motion is coming from.

Eg. I have a fan that minutely giggles a towel that can set off one of my deployments that lacks a PIR.

Options are; put on detective hat or add a PIR (preferably Panasonic PaPir)

I actually do have a PIR on it, but I haven’t been using mmwave for any “off” automation because it’s still too flakey. I have ESPrsense running in that room and it is 90% accurate, I just need mmwave to be able to handle situations where a registered BT device isn’t in the area but people are. I think I just need at least one more mmwave sensor up there.

I can confirm that the HS2643A variant is better at distance but have yet to quantify “better” out side of, “the DFRobot/HS2A43A was on the cusp of detection at the needed distance (4M) and the HS2643A solved that specific use-case.”

Do you have a source on where to purchase the HS2643A? I am not seeing it come up anywhere.

AliExpress link in the OP. Add a comment to the order requesting the variant you want.

Ah thanks, missed the part about it being a special order

Most of my false alerts clear within 15 seconds (with a 10 second latency set), so I’d like to try doing a delay_on filter on the mmwave device, and just rely on the PIR for instant detection.

I have ito setup using option 1 in the OP, but I am not sure how to implement a delay filter with it configured with substitutions to a header file. Would someone mind pointing me in the right direction?

If you use HA automation you can put this extra delay into the trigger. Easy and flexible

Yeah, I am just trying to keep the number of entities I’m managing down

Hi there. Thanks for the great code. I seem to be having a problem with the config settings updating in HA. For example, if I update the distance and refresh the device page, the config box for distance will still be empty. Is that how it works?

When I look at the logs in ESPHome, it looks like it’s setting the distance. It just doesn’t show up in HA?

[23:29:15][D][number:054]: 'gym_distance' - Setting number value
[23:29:15][D][number:113]:   New number value: 3.000000
[23:29:15][D][switch:017]: 'gym_mmwave_sensor' Turning OFF.
[23:29:15][D][switch:037]: 'gym_mmwave_sensor': Sending state OFF
[23:29:15][D][uart_debug:158]: >>> "sensorStop"
[23:29:15][D][uart_debug:158]: <<< "sensorStop\r\n"
[23:29:15][D][uart_debug:158]: <<< "Done\r\n"
[23:29:15][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:16][D][uart_debug:158]: >>> "setRange 0 3.00"
[23:29:16][D][uart_debug:158]: <<< "setRange 0 3.00\r\n"
[23:29:16][D][uart_debug:158]: <<< "Done\r\n"
[23:29:16][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:17][D][switch:013]: 'gym_mmwave_sensor' Turning ON.
[23:29:17][D][switch:037]: 'gym_mmwave_sensor': Sending state ON
[23:29:17][D][uart_debug:158]: >>> "setUartOutput 1 0"
[23:29:17][D][uart_debug:158]: <<< "setUartOutput 1 0\r\n"
[23:29:17][D][uart_debug:158]: <<< "Done\r\n"
[23:29:17][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:17][D][api:102]: Accepted ::FFFF:C0A8:13C
[23:29:17][W][api.connection:080]: Home Assistant 2022.9.6 (::FFFF:C0A8:13C): Connection closed
[23:29:18][D][uart_debug:158]: >>> "setUartOutput 2 1 1 2"
[23:29:18][D][uart_debug:158]: <<< "setUartOutput 2 1 1 2\r\n"
[23:29:18][D][uart_debug:158]: <<< "Done\r\n"
[23:29:18][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:19][D][uart_debug:158]: >>> "saveConfig"
[23:29:19][D][uart_debug:158]: <<< "saveConfig\r\n"
[23:29:19][D][uart_debug:158]: <<< "save cfg complete\r\n"
[23:29:19][D][uart_debug:158]: <<< "Done\r\n"
[23:29:19][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:19][D][uart_debug:158]: >>> "getSensitivity\r"
[23:29:19][D][uart_debug:158]: <<< "getSensitivity\r\n"
[23:29:19][D][uart_debug:158]: <<< "Response 4\r\n"
[23:29:19][D][uart_debug:158]: <<< "Done\r\n"
[23:29:19][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:22][D][binary_sensor:036]: 'gym-mmwave': Sending state OFF
[23:29:22][D][uart_debug:158]: >>> "sensorStartgetRange\r"
[23:29:22][D][uart_debug:158]: <<< "sensorStart\r\n"
[23:29:22][D][uart_debug:158]: <<< "Done\r\n"
[23:29:22][D][uart_debug:158]: <<< "leapMMW:/>"
[23:29:29][D][binary_sensor:036]: 'gym-mmwave': Sending state ON
[23:29:29][D][uart_debug:158]: <<< "$JYRPO,1,1,0.586, ,1.188, , *\r\n"
[23:29:31][D][uart_debug:158]: <<< "$JYRPO,1,1,0.555, ,1.469, , *\r\n"
[23:29:33][D][uart_debug:158]: <<< "$JYRPO,1,1,0.578, ,1.719, , *\r\n"
[23:29:34][D][uart_debug:158]: >>> "getLatency\r"
[23:29:34][D][uart_debug:158]: <<< "getLatency\r\n"
[23:29:35][D][uart_debug:158]: <<< "Response 0.100 12.000\r\n"
[23:29:35][D][uart_debug:158]: <<< "Done\r\n"
[23:29:35][D][uart_debug:158]: <<< "leapMMW:/>$JYRPO,1,1,0.570, ,2.250, , *\r\n"
[23:29:37][D][uart_debug:158]: <<< "$JYRPO,1,1,0.563, ,2.250, , *\r\n"
[23:29:39][D][uart_debug:158]: <<< "$JYRPO,1,1,0.555, ,1.594, , *\r\n"
[23:29:41][D][uart_debug:158]: <<< "$JYRPO,1,1,0.555, ,1.031, , *\r\n"
[23:29:43][D][uart_debug:158]: <<< "$JYRPO,1,1,0.547, ,0.719, , *\r\n"
[23:29:45][D][uart_debug:158]: <<< "$JYRPO,1,1,0.523, ,1.031, , *\r\n"
[23:29:47][D][uart_debug:158]: <<< "$JYRPO,1,1,0.555, ,1.406, , *\r\n"

Any ideas? And if this is the wrong place to ask, can you point me in the right direction?

Nope. Can you try two things?

  1. Factory reset the sensor. Wait 10 seconds. Unplug it for 10 seconds. Plug it back in.
  2. Try ref: use_blocking_scripts

Apparently I already had that code with the block scripts because I cloned the repo and checked out the main branch, where it was committed.

packages:
  uart: !include libraries/esphome-hs2xx3a-custom-component/packages/uart_debug.yml
  leapmmw_sensor: !include libraries/esphome-hs2xx3a-custom-component/packages/leapmmw_sensor.yml

  #remote_package:
    #url: https://github.com/hjmcnew/esphome-hs2xx3a-custom-component
    #ref: release
    #files: [packages/uart.yml, packages/leapmmw_sensor.yml]
    # For additional debugging replace the above line with:
    #files: [packages/uart_debug.yml, packages/leapmmw_sensor.yml]

But let me try it with the remote package option anyway

Ok it’s working now, but the only difference really was that I changed my local copy in the yaml file to include a room name in the entities “name:” field.

Diff results follow: < is the original and > is my modified version.

<     name: mmwave_presence_detection
---
>     name: ${device_name}
49c49
<     name: target_1_distance_m
---
>     name: ${room_name}_target_1_distance_m
54c54
<     name: target_2_distance_m
---
>     name: ${room_name}_target_2_distance_m
59c59
<     name: target_3_distance_m
---
>     name: ${room_name}_target_3_distance_m
64c64
<     name: target_4_distance_m
---
>     name: ${room_name}_target_4_distance_m
69c69
<     name: target_1_SNR
---
>     name: ${room_name}_target_1_SNR
74c74
<     name: target_2_SNR
---
>     name: ${room_name}_target_2_SNR
79c79
<     name: target_3_SNR
---
>     name: ${room_name}_target_3_SNR
84c84
<     name: target_4_SNR
---
>     name: ${room_name}_target_4_SNR
89c89
<     name: num_targets
---
>     name: ${room_name}_num_targets
105c105
<     name: show_target_stats
---
>     name: ${room_name}_show_target_stats
113c113
<     name: mmwave_sensor
---
>     name: ${room_name}_mmwave_sensor
125c125
<     name: led
---
>     name: ${room_name}_led
174c174
<     name: distance
---
>     name: ${room_name}_distance
211c211
<     name: latency
---
>     name: ${room_name}_latency
248c248
<     name: sensitivity
---
>     name: ${room_name}_sensitivity

So I take it from this that you can’t modify the names of the entities? Or did I do it wrong? Because I plan to have at least 3 of these sensors and I want to be able to distinguish between them.

Use a postfix, the prefix target_# is special…

Hi,

I have followed the instruction to the letter and the sensor does appear in home assistant however he doesn’t detect any motion and the led on the sensor is blinking rapidly (whether or not I enable the led in the settings via home assistant) some help would be greatly appreciated!

It wasn’t working for me with a postfix either, so I sort of gave up on it. Until I added my second sensor, and the config values weren’t showing up there. My entities took on the names: number.distance_2, number.latency_2, etc. And the publish wasn’t working because it’s looking for an exact match.

So instead of bugging you about it, I just took it upon myself to find a way around it (a hack) and I got something. In the header file, I just made the following code adjustment.

Change

void publishNumber (std::string sensor, float resp) {
    auto get_numbers = App.get_numbers();
    for(int i = 0; i < get_numbers.size(); i++) {
      auto name = get_numbers[i]->get_name();
      if(name.size() > 6 && name == sensor) {
        get_numbers[i]->publish_state(resp);
      }
    }
  };

To

void publishNumber (std::string sensor, float resp) {
    auto get_numbers = App.get_numbers();
    for(int i = 0; i < get_numbers.size(); i++) {
      auto name = get_numbers[i]->get_name();
      std::size_t found = name.find(sensor);
      if(name.size() > 6 && found!=std::string::npos) {
        get_numbers[i]->publish_state(resp);
      }
    }
  };

Now instead of the entity name needing to be an exact match of the passed variable sensor to publish the state, the sensor variable just needs to be contained within the entity name. So I can name my entities distance_${room}, ${room}_latency, as long as it contains that keyword.

Unfortunately my gym sensor seems to have some other problem (even before the code change) where it would call abort() and basically crash and go into a reboot loop. But my office sensor isn’t doing that so I’ll chalk it up to a hardware issue.

Interesting solution

Has anybody found a datasheet of the leap / DFRobot sensor that describes more of the serial commands? I’ve found marketing material, it references some kind of user guide, but I can’t find it anywhere.

I’m curious for example whether we can separate motion detection from presence detection by setting a higher motion threshold for going from 0 to 1 on the GPIO output and a lower one (+ the standard timeout setting) to go from 1 back to 0. That would avoid the need for combination with a PIR sensor.

Have not seen this described anywhere for these sensors.

I had the same problem. After swapping TX and RX GPIO pin configuration in the yaml file it worked.

I’m having some weird behavior with my setup. Occasionally it will call abort() and reboot itself, which makes for a slight annoyance with my automations because the PIR sensor I have attached goes to an On state after reboot and turns on my lights when nobody is actually there.

FYI, I’m using an ESP32 DevkitV1 with BLE sensors, a PIR sensor (AM312), and the DFRobot mmwave sensor (SEN0395)

My yaml looks like this:

esphome:
  name: "gym-presence"

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:


wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  
  manual_ip:
    static_ip: 192.168.1.207
    gateway: 192.168.1.1
    subnet: 255.255.255.0

substitutions:
  device_name: gym-mmwave
  room_name: gym
  uart_tx_pin: GPIO14
  uart_rx_pin: GPIO27
  gpio_pin: GPIO13
  header_file: libraries/leapmmw_sensor.h
  
packages:
  uart: !include libraries/esphome-hs2xx3a-custom-component/packages/uart_debug.yml
  leapmmw_sensor: !include libraries/leapmmw_sensor.yml

  #remote_package:
    #url: https://github.com/hjmcnew/esphome-hs2xx3a-custom-component
    #ref: use_blocking_scripts
    #files: [packages/uart.yml, packages/leapmmw_sensor.yml]
    # For additional debugging replace the above line with:
    #files: [packages/uart_debug.yml, packages/leapmmw_sensor.yml]
  
  
#web_server:
#  port: 80
#  version: 2
#  include_internal: true
  
#http_request:
#  useragent: esphome/$device_name
#  timeout: 2s
  

esp32_ble_tracker:

binary_sensor:
  - platform: ble_presence
    ibeacon_uuid: 1b566cec-05de-4d8c-a826-62a2bc413012
    ibeacon_major: 100
    ibeacon_minor: 1
    name: "Samsung Galaxy Z Fold 4 - gym"
  - platform: ble_presence
    ibeacon_uuid: 6f6ee132-3ca9-404b-adc4-795511b3d479
    ibeacon_major: 100
    ibeacon_minor: 1
    name: "Samsung Galaxy phone - gym"
  - platform: gpio
    pin: 
      number: GPIO16
      mode: INPUT_PULLDOWN
    name: "Gym PIR motion"
    device_class: motion
    filters:
      delayed_off: 10s

I am using local copies of the leapmmw.yaml and leapmmw.h files, pulled from the main branch just about a week ago, With one slight modification in the .h file to get HA to read my custom sensor names.

Has anybody else experienced this? Trying to figure out if it’s hardware or software issue. I’ve already tried multiple esp32 boards (and multiple different pins) and even multiple sen0395s.