mmWave Presence Detection - ESPHome style

I also received my sensors today. They work great so far. Can I disable over software/configuration the red blinking led on the sensor?

Use tape? :stuck_out_tongue:

I don’t see a software option in the wiki.

I had a lot of trouble with false positives using the RCWL sensors. Since it looks like some of you have been using them now for a while, how has it been so far?

What I understand from this topic they will be detect the smallest movement. Since I have a cat I expect I won’t be able to use it in most places anyway, but a bed occupancy sensor seems possible with some extra logic. However, I would have to be able to ignore moving curtains, flying bugs, plant leave moving in the wind - we often have the windows open - things like that. Or restrict it very specifically to just the bed somehow.

By themselves, the smallest movement reliably triggers them. So the breeze from an open window rustling papers on a desk, curtains, plants, etc will set them off. I haven’t tested bugs though…

I have cats - they do indeed set these off. Pairing with a PIR can help mitigate that and the previously mentioned sources. Likewise careful placement if possible, but cats go everywhere; Suki!!

Alternatively, if you need merely occupancy and not response time, you could use a delayed_on configuration for similar debouncing of non-sustained movements.

What is the PIR for, to trigger the initial state? And then use the microwave sensor to more reliably detect if there’s still a presence? I’m just a bit hesitant to start trying presence detection again. After a lot of tries and invested time (including bayesian sensors and the like) I simply gave up on anything but home/not home.

I’m just spitballing here, but maybe two microwave sensors can be combined (probably using two different wavelengths)? One to detect ‘big’ movement so that a human can be distinguished and the other to detect ‘fine’ movement to confirm that the human is still there.

1 Like

You got it.

I concur that reliable presence is challenging. And it has taken experimentation and patience to work out a good solution. Even the best sensor, aimed poorly, can be frustrating. I’ve been there.

Combining mmWave is possible. But I haven’t seen one that differentiates big or small. The K-LD series offer a threshold/sensitivity adjustment. But having played with them I can tell you that these are far more effort in order to tune.

Here is the next iteration of this project that uses PIR and mmWave, Low-latency presence sensor: mmWave + PIR using ESPHome

This has been a great thread, which I’ve taken and implemented using the Seeed 24GHz sensor in ESPHome.

My yaml file

substitutions:
# Change the disp_name to something you want  
  display_name: Office presence
# Interval of how often the power is updated
  update_time: 1s
  
esphome:
  name: office-presence-sensor
  comment: ${display_name}
  includes:
    - uart_read_radar_sensor.h
  libraries:
    - "Seeed Arduino 24GHz Radar Sensor"

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: something
  password: something
  fast_connect: true
#  power_save_mode: none
  manual_ip:
    static_ip: 192.168.1.33
    gateway: 192.168.1.1
    subnet: 255.255.255.0
    dns1: 192.168.1.1

# Enable logging
logger:
  level: INFO #makes uart stream available in esphome logstream

# Enable Home Assistant API
api:
  services:
      # Service to send a command directly to the display. Useful for testing
    - service: send_command
      variables:
        cmd: string
      then:
        - uart.write: !lambda
                        char buf[128];
                        sprintf(buf, "%s\n", cmd.c_str());
                        std::string s = buf;
                        return std::vector<unsigned char>( s.begin(), s.end() );

ota:
  password: "s"

uart:
  id: uart_bus
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 9600
#  debug:

# Example configuration entry
binary_sensor:
  - platform: gpio
    pin: GPIO21
    name: ${display_name} Presence
    device_class: motion
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms
  - platform: gpio
    pin: GPIO22
    name: ${display_name} Moving
    device_class: moving
    filters:
      - delayed_on: 100ms
      - delayed_off: 500ms
sensor:
  - platform: custom
    lambda: |-
      auto my_custom_sensor = new UartReadRadarSensor(id(uart_bus));
      App.register_component(my_custom_sensor);
      return {my_custom_sensor->action_status, my_custom_sensor->movement_status };
    sensors:
      - name: ${display_name} Action
        id: office_presence_action
      - name: ${display_name} Movement
        id: office_presence_movement

# Example configuration entry
text_sensor:
  - platform: template
    name: "${display_name} Action Description"
    id: office_presence_action_description
    lambda: |-
      if( id(office_presence_action).state == 2 ) {
        return{ "Movement" };
      }
      else if( id(office_presence_action).state == 3 ) {
        return{ "Stationary" };
      }
      else if( id(office_presence_action).state == 4 ) {
        return{ "No-movement" };
      }
      else if( id(office_presence_action).state == 5 ) {
        return{ "Closer" };
      }
      else if( id(office_presence_action).state == 6 ) {
        return{ "Further" };
      }
      return {"Vacant"};
    update_interval: ${update_time}
  - platform: template
    name: "${display_name} Movement Description"
    id: office_presence_movement_description
    lambda: |-
      if( id(office_presence_movement).state == 1 ) {
        return{ "Still" };
      }
      else if( id(office_presence_movement).state == 2 ) {
        return{ "Micro" };
      }
      else if( id(office_presence_movement).state == 3 ) {
        return{ "Slow" };
      }
      else if( id(office_presence_movement).state == 4 ) {
        return{ "Fast" };
      }
      return {"None"};
    update_interval: ${update_time}

My uart_read_radar_sensor.h file

#include "esphome.h"
#include "radar.h"

#define MESSAGE_HEAD 0x55
#define NEWLINE 0x0a

class UartReadRadarSensor : public Component, public UARTDevice {
public:
  radar RADAR;
  Sensor *action_status = new Sensor();
  Sensor *movement_status = new Sensor();

  UartReadRadarSensor(UARTComponent *parent) : UARTDevice(parent) {}

  void setup() override {
    // nothing to do here
  }

  int Bodysign_judgment(int ad1, int ad2, int ad3, int ad4, int ad5){
    float s;
    s = RADAR.Bodysign_val(ad1, ad2, ad3, ad4, ad5);

    ESP_LOGV("custom", "Bodysign_val = %f", s );

    if(s < 1.0){
      return 0; // NOBODY;
    }
    else if(s < 2.0){
      return 1; // STATIONARY;
    }
    else if(s >= 2.0 && s < 30.0){
      return 2; // MICRO MOVEMENTS;
    }
    else if(s >= 30.0 && s < 60.0){
      return 3; // SLOW MOVEMENT;
    }
    else if(s >= 60.0){
      return 4; // FAST MOVEMENT;
    }
    return -1;
  }

  int readline(int readch, int *thisMessage, int len)
  {
    static int pos = 0;
    int rpos;

    ESP_LOGV("custom", "this char = %2x; pos = %i", readch, pos );

    // if the read char is not the marker and the pos is zero, skip 
    // i.e. skip until the start marker is found
    if( readch != MESSAGE_HEAD && pos == 0 ){
      return -1;
    }

    // if the read char is a newline and the pos is 1, store the data 
    // I find i'm getting 0x55, then 0x0a, then the good data
    if( readch == NEWLINE && pos == 1 ){
      if (pos < len-1) {
        thisMessage[pos++] = readch;
        thisMessage[pos] = 0;
      }
      return -1;
    }

    // if the read char is the marker and the pos is not zero, return result 
    if( readch == MESSAGE_HEAD && pos != 0 ){
      rpos = pos;
      pos = 0;  // Reset position index ready for next time
      return rpos;
    }

    if (readch != -1) {
      switch (readch) {
//        case '\r': // Return on CR
//          break;
        case NEWLINE: // Return on new-line
          rpos = pos;
          pos = 0;  // Reset position index ready for next time
          return rpos;
        default:
          if (pos < len-1) {
            thisMessage[pos++] = readch;
            thisMessage[pos] = 0;
          }
      }
    }

    // No end of line has been found, so return -1.
    return -1;
  }

  void loop() override {
    const int max_line_length = 14;
    static int thisMessage[max_line_length];
    int activity_result = 0;
    int movement_result = 0;

    while (available()) {
      if(readline(read(), thisMessage, max_line_length) > 0) {

        activity_result = RADAR.Situation_judgment(thisMessage[4], thisMessage[5], thisMessage[6], thisMessage[7], thisMessage[8]);
        ESP_LOGD("custom", "The value of sensor is: %i = RADAR.Situation_judgment( %2x, %2x, %2x, %2x, %2x, )", activity_result, thisMessage[4], thisMessage[5], thisMessage[6], thisMessage[7], thisMessage[8] );
        if( activity_result > 0 ) {
          action_status->publish_state( activity_result );
        }

        movement_result = Bodysign_judgment(thisMessage[5], thisMessage[6], thisMessage[7], thisMessage[8], thisMessage[9]);
        ESP_LOGD("custom", "The value of sensor is: %i = Bodysign_judgment( %2x, %2x, %2x, %2x, %2x, )", movement_result, thisMessage[5], thisMessage[6], thisMessage[7], thisMessage[8], thisMessage[9] );
        if( thisMessage[5] == 6 ){
          movement_status->publish_state( movement_result );
        }
      }
    }
  }
};
7 Likes

I can confirm my 24GHz Seeed sensor can see through single brick walls! It is very responsive and will trigger movement almost immediately

1 Like

any links to it?

Yes - i updated my post above

Absolutely, it’d be great if someone designed a case for this sensor, I can’t find any on thingiverse sadly.
Anyone here knows how to CAD ?

Get the hobby license for Fusion360, it is not as hard as it may seem at first. It will take some practice, and you need a decent set of calipers, but I expect anyone that can a operate a 3dprinter can design a case.

You can also always just glue it into a ready made box, you can get those cheap of aliexpres.

I don’t have the sensor - for now - so I can’t design it myself sadly.

Hi, what’s the most ideal location/direction to place the sensor in a bedroom if I need to avoid getting false detection from other rooms?
Center ceiling or on table next to bed perhaps?

Based on your drawings, i would place in the ceiling pointing down, over the middle of the foot of the beds. If possible, I would ‘test fit’ to see if the sensors are triggered by movement outside of the room. The other option would be on top of the wardrobes, pointing at the beds.

1 Like

Thanks, as for smaller space like the bathroom
Could I limit the detection range within the bathroom area since it might penetrate the bedroom walls?

Yes, distance is configurable. I have four DFRobot mmwave and zero see through my walls. Typical North American construction of 1/2" drywall with 2x4 framing.

The DFRobot bounces off my walls and increasing detection distance for those reflections may easily confuse some to believe that it sees thru them.

Other sensors such as the SeedStudio indicate in their datasheet that seeing through material is by design. I have seen no such evidence for the DFRobot.

1 Like

You’re right, I made a case in Fusion 360, wasn’t that hard. It fits pretty well and has two little screw standoffs, I’ll publish it on thingiverse once I get the screw holes working.

I’ve been trying to connect the UART on my SEN0395 but I can’t get anything back, do people have it working just using the config in the first post ? I tried adding the esphome’s debug to see what it sends back but I never get any output from the sensor, not sure why.

A new mains powered ZigBee mmWave to compete with the Aqara FP1. Where the Aqara has a ~5 sec detection time, the Lifesmart is 0.5 sec with a 2 minute cooldown.

https://iot.ilifesmart.com/smart-sensor/human-presence-sensor.html

Thanks, is there an example for distance adjustment and can that be used with the seeed sensors? Apparently it will be restocked within 2 weeks

Haven’t used them, sorry. Suggest referring to the datasheet for available options.