mmWave human presence for under $20?!

Note:

After extensive testing, I don’t really like the Seeed Human Presence sensor that much for sleep detection. Somewhat similar issues with people sitting but since there’s more movement you can set the sensitivity really low and while still avoiding getting many incorrect unoccupied readings because of people moving around a little bit while watching tv, etc.

If you want to detect sleep from beneath (through) your bed, use the DFRobot one. I trust my daily routine / alarm on the DFRobot sensor detecting if I’m in bed or not.

The Seeed one can have a hard time returning back to unoccupied (sometimes doesn’t for hours). If you set the sensitivity to lower, it can miss you sleeping entirely.

DFRobot does really well on stabilizing at a 1=occupied, 0=unoccupied (after about 15-30s to occupied, about 30-60s to unoccupied) even though it has much less configuration and “features”, it does the one thing better (you have to filter out some blips which is easy to do).

4 Likes

Thanks for the update and advice. I also noted that on the Seeed studio model, it sometimes takes forever to get to the vacant state, despite nobody being present in its vicinity. I have been trying to get my hands on the DFRobot model for some time, but being in India, I have very few sources.

Let me know if you have any trouble with the checksum code!

1 Like

In the other thread you previously shared, @crlogic pointed out that in the manual of the chip, it recommends to use 2 capacitors instead of connecting it straight away to the 5V pin, could it be that your issues are because of that?

Hi @phillip1 - amazing thread! Appreciate the code for the SeeedStudio. I have a question about the sensitivity adjustment posted above. Please note; this is coming from someone w/ no cpp knowledge. But I can read the formatting as well as copy & paste!

Question: where is _s declared?

I am trying to make the sensitivity HA adjustable, aka

esphome:
  name: poe-seedstudio-mmwave
  includes:
    - philips_seeedstudio_sensitivity.h
number:
  - platform: template
    name: sensitivity
    id: sensitivity
    min_value: 1
    max_value: 10
    initial_value: 2
    step: 1
    restore_value: true
    set_action:
      - delay: 2s
      - uart.write: !lambda
            if (x == 1) {
              send_write_SystemParameter_ThresholdGear_1();
              return {};
            }
            else if (x == 2) {
              send_write_SystemParameter_ThresholdGear_2();
              return {};
            }
            else if (x == 3) {
              send_write_SystemParameter_ThresholdGear_3();
              return {};
            }

I have reviewed the thread a couple times but seems to miss any prior .h where serial is perhaps setup?

Check out the RFBeam K-LD2 if you are interested in approaching/receding detection. Works well for me the past few months. I use one to trigger motion lights around a corner where the primary motion sensor is also around that corner. Approaching = trigger lights before primary sensor. Receding = allow primary sensor to trigger lights off. The interesting part of the K-LD2 is that is turns off on static presence. So just “standing around the corner” doesn’t mean triggering the lights.

Alternatively, the K-LD7 provides speed, distance and angle which is fun when you make WLED blink a light that follows you around the room.

1 Like

That’s a super neat sensor, might just have to get one.

I have updated the code I posted previously in the “Here is the code” block to give you an idea on how to setup the serial. I think you may need to use a custom component.

To simplify your code, you can add a sendThreshold function

void sendThreshold(int threshold) {
  unsigned char thresholdPacket[4] = {0x02, 0x04, 0x0C, threshold};
  sendMessage(thresholdPacket, 4);
}

Then call it with sendThreshold(x); inside your !lambda.

I think the part the confuses me is that the ESPHome Custom UART shows read examples and we are trying to write.

Hence my usage of the ESPHome uart.write yaml which I have successfully used in the past.

Following this simplified strategy to feed uart.write with bytearray string to change the threshold. Can you see why the following “not declared in this scope” error are occurring? I makes not sense to me given I have paired down the existing code which didn’t have such errors.

esphome:
  name: poe-seedstudio-mmwave
  includes:
    - uart_seeedstudio_sensitivity.h
const unsigned char cuc_CRCHi[256] = {
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40
};

const unsigned char cuc_CRCLo[256]= {
  0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
  0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
  0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
  0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
  0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
  0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
  0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
  0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
  0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
  0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
  0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
  0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
  0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
  0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
  0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
  0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
  0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
  0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
  0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
  0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
  0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
  0x41, 0x81, 0x80, 0x40
};

unsigned short int us_CalculateCrc16(unsigned char *lpuc_Frame, unsigned short int lus_Len){
  unsigned char luc_CRCHi = 0xFF;
  unsigned char luc_CRCLo = 0xFF;
  int li_Index=0;
  while(lus_Len--){
    li_Index = luc_CRCLo ^ *( lpuc_Frame++);
    luc_CRCLo = (unsigned char)( luc_CRCHi ^ cuc_CRCHi[li_Index]);
    luc_CRCHi = cuc_CRCLo[li_Index];
  }
  return (unsigned short int )(luc_CRCLo << 8 | luc_CRCHi);
}

char CRC(char ad1, char ad2, char ad3, char ad4, char ad5, char ad6, char ad7){
  unsigned char data[] = {ad1, ad2, ad3, ad4, ad5, ad6, ad7};
  unsigned short int crc_data = 0x0000;
  unsigned int lenth = sizeof(data)/sizeof(unsigned char);
  crc_data = us_CalculateCrc16(data, lenth);
  return crc_data;
}

char sendThreshold(int threshold) {
  unsigned char thresholdPacket[4] = {0x02, 0x04, 0x0C, threshold};
  len = 7;
  unsigned char data[len];
  unsigned char dataWithChecksum[len + 2];
  data[0] = 0x55;
  data[1] = len + 1;
  data[2] = 0x00;   
  for (int n = 0; n < len - 3; n++) {
    data[n + 3] = thresholdPacket[n];
  }
  for (int n = 0; n < len; n++) {
    dataWithChecksum[n] = data[n];
  }
  unsigned short int crc_data = us_CalculateCrc16(data, len);
  dataWithChecksum[len] = (crc_data & 0xff00) >> 8;
  dataWithChecksum[len+1] = crc_data & 0xff; 
  return dataWithChecksum;
}
number:
  - platform: template
    name: sensitivity
    id: sensitivity
    min_value: 1
    max_value: 9
    initial_value: 3
    step: 1
    restore_value: true
    set_action:
      - delay: 2s
      - uart.write: !lambda
                      return sendThreshold(x);
Compiling /data/poe-seedstudio-mmwave/.pioenvs/poe-seedstudio-mmwave/src/main.cpp.o
In file included from src/main.cpp:51:0:
src/uart_seeedstudio_sensitivity.h: In function 'char sendThreshold(int)':
src/uart_seeedstudio_sensitivity.h:73:66: warning: narrowing conversion of 'threshold' from 'int' to 'unsigned char' inside { } [-Wnarrowing]
   unsigned char thresholdPacket[4] = {0x02, 0x04, 0x0C, threshold};
                                                                  ^
src/uart_seeedstudio_sensitivity.h:74:3: error: 'len' was not declared in this scope
   len = 7;
   ^
src/uart_seeedstudio_sensitivity.h:77:3: error: 'data' was not declared in this scope
   data[0] = 0x55;
   ^
src/uart_seeedstudio_sensitivity.h:84:5: error: 'dataWithChecksum' was not declared in this scope
     dataWithChecksum[n] = data[n];
     ^
src/uart_seeedstudio_sensitivity.h:87:3: error: 'dataWithChecksum' was not declared in this scope
   dataWithChecksum[len] = (crc_data & 0xff00) >> 8;
   ^
/config/esphome/poe-seedstudio-mmwave.yaml: In lambda function:
/config/esphome/poe-seedstudio-mmwave.yaml:126:27: error: could not convert 'sendThreshold((int)x)' from 'char' to 'std::vector<unsigned char>'
       - uart.write: !lambda
                           ^
*** [/data/poe-seedstudio-mmwave/.pioenvs/poe-seedstudio-mmwave/src/main.cpp.o] Error 1
========================== [FAILED] Took 2.98 seconds ==========================

Your code looks good you just scrubbed some declarations from the code. I don’t know esphome well, so uart.write is new to me (I just do it all with 10x more c++ code so I can put off learning esphome).

Try this (simpler):

//make the code more concise, but still work the same:
std::vector<unsigned char> getThresholdPacket(int threshold) {
  int len = 7;
  unsigned char thresholdPacket[] = {0x55, (unsigned char)(len+1), 0x00, 0x02, 0x04, 0x0C, (unsigned char)threshold};

  unsigned short int crc_data = us_CalculateCrc16(thresholdPacket, len);

  std::vector<unsigned char> dataWithChecksum;
  dataWithChecksum.assign(thresholdPacket, thresholdPacket + len);
  dataWithChecksum.push_back((crc_data & 0xff00) >> 8);
  dataWithChecksum.push_back(crc_data & 0xff); 

  return dataWithChecksum;
}
1 Like

That compiles!

Snippet of log when changing values;

[19:47:43][D][number:113]:   New number value: 2.000000
[19:47:45][D][uart_debug:114]: >>> 55:08:00:02:04:0C:02:DB:F5
[19:47:45][D][uart_debug:114]: <<< 55:08:00:02:04:0C:02:DB:F5
[19:47:51][D][number:054]: 'sensitivity' - Setting number value
[19:47:51][D][number:113]:   New number value: 1.000000
[19:47:53][D][uart_debug:114]: >>> 55:08:00:02:04:0C:01:9B:F4
[19:47:53][D][uart_debug:114]: <<< 55:08:00:02:04:0C:01:9B:F4

Will have to do some testing as this SeeedStudio is unfamiliar to me.

The DFRobot can be tuned down to mere CM’s distance which is helpful for testing purposes.

1 Like

Thank you very much for your review! I hoped that I can use the Seeed 60Ghz as a sleep and bett sensor.
Is it possible for you to post an esphome yaml for presence, breathing and heart rate to test?

1 Like

presence_sensor.h

#include "esphome.h"
#include "Radar.h"

using namespace esphome;

static const char* TAG = "PresenceSensor";

class PresenceSensor : public Component, public UARTDevice {
  public:

  Radar* radar = new Radar(&Serial);

  Sensor* presence_sensor = new Sensor();

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

  void setup() {
    // delay(1000);    
    // radar->send_write_SystemParameter_ThresholdGear_1();

    // delay(1000);
    // radar->send_write_SystemParameter_SceneSetting_LivingTopMounted();
  }

  std::vector<int> bytes;

  void loop() override {  
    while(available() > 0) {
      int incomingByte = read();
      
      //hit next message
      if(incomingByte == 85) {
        //could check if checksum matches if you feel like it (last 2 bytes)
        
        processPacket();

        bytes.clear();
        bytes.push_back(85);             
      } else {              
        bytes.push_back(incomingByte);     
      }
    }    
  }


  std::vector<float> lastLevels = std::vector<float>(4, 0.0f);

  void processPacket() {            
    //the array could look like 85 7 0 4 3 0 1 1 1 137 92
    //cut off the first 3 header bytes and the last 2 checksum bytes

    bytes.erase(bytes.begin(), bytes.begin() + 3);
    bytes.resize(bytes.size() - 2); //remove last 2 checksum bytes
    
    std::string str = "";


    //NOW YOU HAVE THE DATA PACKET BYTES. DO WHATEVER YOU WANT

    //this is the one you really only care about
    //to get the movement amount
    if((bytes[0] == 0x03 || bytes[0] == 0x04) && (bytes[1] == 0x03) && (bytes[2] == 0x06))
    {        
      str = "Proactive Radar Signs: " + esphome::to_string(getFloat(bytes[3], bytes[4], bytes[5], bytes[6]));
      ESP_LOGI(TAG, str.c_str());
      
      float newSensorValue = getFloat(bytes[3], bytes[4], bytes[5], bytes[6]);
            
      lastLevels.insert(lastLevels.begin(), newSensorValue);
      
      //sometimes when movement starts, the sensor jumps down to zero for one or two readings
      //this fixes that behaviour
      if(newSensorValue < 0.1f && (lastLevels[0] > 5 || lastLevels[1] > 5 || lastLevels[2] > 5 || lastLevels[3] > 5)) {
        //don't publish
      } else {
        presence_sensor->publish_state(newSensorValue);
      }            
      
      return;
    }
    
    if((bytes[0] == 0x04) && (bytes[1] == 0x03) && (bytes[2] == 0x07))
    {
      str = "Proactive Radar Approaching: " + hexStr(bytes[5]);
      ESP_LOGI(TAG, str.c_str());       
      
      return;
    }
    if((bytes[0] == 0x03 || bytes[0] == 0x04) && (bytes[1] == 0x03) && (bytes[2] == 0x05)) //env
    {
      str += "Proactive Radar Env:";
      if(bytes[3] == 0x00 && bytes[4] == 0xFF && bytes[5] == 0xFF)
      {
        str += " Unoccupied";
      }
      else if(bytes[3] == 0x01 && bytes[4] == 0x00 && bytes[5] == 0xFF)
      {
        str += " Stationary";
      }
      else if(bytes[3] == 0x01 && bytes[4] == 0x01 && bytes[5] == 0x01) 
      {
        str += " Exercise";
      }
      else
      {
        str += " Other?";
      }
      ESP_LOGI(TAG, str.c_str());  
      
      return;
    }
    //toss away most of the data (headers, length byte, checksum)
    for (int i = 0; i < bytes.size(); i++) {                
      int code = bytes[i];
      if(i == 0) {
        if(code == 0x03)
        {
          str += "Passive ";
        } 
        else if (code == 0x04)
        {
          str += "Proactive ";
        }
      } else if(i == 1) {
        if(code == 0x03)
        {
          str += "Radar ";
        } 
      } else if(i == 2) {
        if(code == 0x05)
        {
          str += "Env ";
        } 
        else if (code == 0x06)
        {
          str += "Signs ";
        }
      } else {
        str += hexStr(bytes[i]) + " ";                                    
      }      
    }
    
    ESP_LOGI(TAG, str.c_str());
  }

  char hexmap[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

  std::string hexStr(int data)
  {
    std::string s = "    ";
    s[0] = '0';
    s[1] = 'x';
    s[2] = hexmap[(data & 0xF0) >> 4];
    s[3] = hexmap[data & 0x0F];

    //s = "0x" + s;  
    return s;
  }

  
  typedef union
  {
    unsigned char Byte[4];
    float Float;
  }FloatByte;

  float getFloat(int b1, int b2, int b3, int b4)
  {
    FloatByte fb;
    fb.Byte[0] = b1;
    fb.Byte[1] = b2;
    fb.Byte[2] = b3;
    fb.Byte[3] = b4;

    return fb.Float;
  }
};

Radar.cpp

#include "Arduino.h"
#include "Radar.h"

#define MESSAGE_HEAD 0x55
#define ACTIVE_REPORT 0x04
#define FALL_REPORT 0x06

#define REPORT_RADAR 0x03
#define REPORT_OTHER 0x05

#define HEARTBEAT 0x01
#define ABNOEMAL 0x02
#define ENVIRONMENT 0x05
#define BODYSIGN 0x06
#define CLOSE_AWAY 0x07

#define CA_BE 0x01
#define CA_CLOSE 0x02
#define CA_AWAY 0x03
#define SOMEBODY_BE 0x01
#define SOMEBODY_MOVE 0x01
#define SOMEBODY_STOP 0x00
#define NOBODY 0x00

Radar::Radar(Stream *s)
{
  _s = s;
}

//CHECKSUM CALCULATION

const unsigned char cuc_CRCHi[256]= {
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40
};

const unsigned char cuc_CRCLo[256]= {
  0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
  0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
  0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
  0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
  0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
  0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
  0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
  0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
  0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
  0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
  0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
  0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
  0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
  0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
  0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
  0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
  0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
  0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
  0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
  0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
  0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
  0x41, 0x81, 0x80, 0x40
};


unsigned short int Radar::us_CalculateCrc16(unsigned char *lpuc_Frame, unsigned short int lus_Len){
  unsigned char luc_CRCHi = 0xFF;
  unsigned char luc_CRCLo = 0xFF;
  int li_Index=0;
  while(lus_Len--){
    li_Index = luc_CRCLo ^ *( lpuc_Frame++);
    luc_CRCLo = (unsigned char)( luc_CRCHi ^ cuc_CRCHi[li_Index]);
    luc_CRCHi = cuc_CRCLo[li_Index];
  }
  return (unsigned short int )(luc_CRCLo << 8 | luc_CRCHi);
}

char Radar::CRC(char ad1, char ad2, char ad3, char ad4, char ad5, char ad6, char ad7){
  unsigned char data[] = {ad1, ad2, ad3, ad4, ad5, ad6, ad7};
    unsigned short int crc_data = 0x0000;
    unsigned int lenth = sizeof(data)/sizeof(unsigned char);
    crc_data = us_CalculateCrc16(data, lenth);
    return crc_data;
}



void Radar::sendMessage(unsigned char partialData[], int length)
{  
  length += 3;

  unsigned char data[length];
  unsigned char dataWithChecksum[length + 2];

  data[0] = 0x55;
  data[1] = length + 1; //
  data[2] = 0x00;   

  for (int n = 0; n < length - 3; n++)data[n + 3] = partialData[n];

  for (int n = 0; n < length; n++)dataWithChecksum[n] = data[n];
  unsigned short int crc_data = us_CalculateCrc16(data, length);
  dataWithChecksum[length] = (crc_data & 0xff00) >> 8;
  dataWithChecksum[length+1] = crc_data & 0xff;  
  for (int n = 0; n < length + 2; n++){      
    _s->write(dataWithChecksum[n]);
  }
}


unsigned char read_RadarInfo_EnvironmentalStatus[3] = {0x01, 0x03, 0x05};
unsigned char read_RadarInfo_MotorSigns[3] = {0x01, 0x03, 0x06};
unsigned char read_SystemParameter_ThresholdGear[3] = {0x01, 0x04, 0x0C}; //tested
unsigned char read_SystemParameter_SceneSetting[3] = {0x01, 0x04, 0x10};

unsigned char write_SystemParameter_ThresholdGear_1[4] = {0x02, 0x04, 0x0C, 0x01}; //least sensitive
unsigned char write_SystemParameter_ThresholdGear_2[4] = {0x02, 0x04, 0x0C, 0x02};
unsigned char write_SystemParameter_ThresholdGear_3[4] = {0x02, 0x04, 0x0C, 0x03};
unsigned char write_SystemParameter_ThresholdGear_4[4] = {0x02, 0x04, 0x0C, 0x04};
unsigned char write_SystemParameter_ThresholdGear_5[4] = {0x02, 0x04, 0x0C, 0x05};
unsigned char write_SystemParameter_ThresholdGear_6[4] = {0x02, 0x04, 0x0C, 0x06};
unsigned char write_SystemParameter_ThresholdGear_7[4] = {0x02, 0x04, 0x0C, 0x07}; //default
unsigned char write_SystemParameter_ThresholdGear_8[4] = {0x02, 0x04, 0x0C, 0x08};
unsigned char write_SystemParameter_ThresholdGear_9[4] = {0x02, 0x04, 0x0C, 0x09};
unsigned char write_SystemParameter_ThresholdGear_10[4] = {0x02, 0x04, 0x0C, 0x0A}; //most sensitive
unsigned char write_SystemParameter_SceneSetting_Default[4] = {0x02, 0x04, 0x10, 0x00};   //roughly a distance setting?
unsigned char write_SystemParameter_SceneSetting_AreaTopLoading[4] = {0x02, 0x04, 0x10, 0x01};
unsigned char write_SystemParameter_SceneSetting_BathroomTopMounted[4] = {0x02, 0x04, 0x10, 0x02};
unsigned char write_SystemParameter_SceneSetting_BathroomTopLoading[4] = {0x02, 0x04, 0x10, 0x03};
unsigned char write_SystemParameter_SceneSetting_LivingTopMounted[4] = {0x02, 0x04, 0x10, 0x04};
unsigned char write_SystemParameter_SceneSetting_OfficeTopLoading[4] = {0x02, 0x04, 0x10, 0x05};
unsigned char write_SystemParameter_SceneSetting_HotelTopLoading[4] = {0x02, 0x04, 0x10, 0x06}; //hotel is the largest?


void Radar::send_read_RadarInfo_EnvironmentalStatus() { sendMessage(read_RadarInfo_EnvironmentalStatus, 3); };
void Radar::send_read_RadarInfo_MotorSigns() { sendMessage(read_RadarInfo_MotorSigns, 3); };
void Radar::send_read_SystemParameter_ThresholdGear() { sendMessage(read_SystemParameter_ThresholdGear, 3); };
void Radar::send_read_SystemParameter_SceneSetting() { sendMessage(read_SystemParameter_SceneSetting, 3); };

void Radar::send_write_SystemParameter_ThresholdGear_1() { sendMessage(write_SystemParameter_ThresholdGear_1, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_2() { sendMessage(write_SystemParameter_ThresholdGear_2, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_3() { sendMessage(write_SystemParameter_ThresholdGear_3, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_4() { sendMessage(write_SystemParameter_ThresholdGear_4, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_5() { sendMessage(write_SystemParameter_ThresholdGear_5, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_6() { sendMessage(write_SystemParameter_ThresholdGear_6, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_7() { sendMessage(write_SystemParameter_ThresholdGear_7, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_8() { sendMessage(write_SystemParameter_ThresholdGear_8, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_9() { sendMessage(write_SystemParameter_ThresholdGear_9, 4); };
void Radar::send_write_SystemParameter_ThresholdGear_10() { sendMessage(write_SystemParameter_ThresholdGear_10, 4); };
void Radar::send_write_SystemParameter_SceneSetting_Default() { sendMessage(write_SystemParameter_SceneSetting_Default, 4); };
void Radar::send_write_SystemParameter_SceneSetting_AreaTopLoading() { sendMessage(write_SystemParameter_SceneSetting_AreaTopLoading, 4); };
void Radar::send_write_SystemParameter_SceneSetting_BathroomTopMounted() { sendMessage(write_SystemParameter_SceneSetting_BathroomTopMounted, 4); };
void Radar::send_write_SystemParameter_SceneSetting_BathroomTopLoading() { sendMessage(write_SystemParameter_SceneSetting_BathroomTopLoading, 4); };
void Radar::send_write_SystemParameter_SceneSetting_LivingTopMounted() { sendMessage(write_SystemParameter_SceneSetting_LivingTopMounted, 4); };
void Radar::send_write_SystemParameter_SceneSetting_OfficeTopLoading() { sendMessage(write_SystemParameter_SceneSetting_OfficeTopLoading, 4); };
void Radar::send_write_SystemParameter_SceneSetting_HotelTopLoading() { sendMessage(write_SystemParameter_SceneSetting_HotelTopLoading, 4); };

Radar.h

#ifndef _RADAR_H__
#define _RADAR_H__

#include <Arduino.h>

class Radar
{
    private:
        Stream *_s;
        
    public:
        Radar(Stream *s);

        char CRC(char ad1, char ad2, char ad3, char ad4, char ad5, char ad6, char ad7);
        void sendMessage(unsigned char partialData[], int length);

        unsigned short int us_CalculateCrc16(unsigned char *lpuc_Frame, unsigned short int lus_Len);
        
        void send_read_RadarInfo_EnvironmentalStatus();
        void send_read_RadarInfo_MotorSigns();
        void send_read_SystemParameter_ThresholdGear(); //tested
        void send_read_SystemParameter_SceneSetting();        

        void send_write_SystemParameter_ThresholdGear_1(); //least sensitive
        void send_write_SystemParameter_ThresholdGear_2();
        void send_write_SystemParameter_ThresholdGear_3();
        void send_write_SystemParameter_ThresholdGear_4();
        void send_write_SystemParameter_ThresholdGear_5();
        void send_write_SystemParameter_ThresholdGear_6();
        void send_write_SystemParameter_ThresholdGear_7(); //default
        void send_write_SystemParameter_ThresholdGear_8();
        void send_write_SystemParameter_ThresholdGear_9();
        void send_write_SystemParameter_ThresholdGear_10(); //most sensitive
        void send_write_SystemParameter_SceneSetting_Default(); //roughly a distance setting?
        void send_write_SystemParameter_SceneSetting_AreaTopLoading();
        void send_write_SystemParameter_SceneSetting_BathroomTopMounted();
        void send_write_SystemParameter_SceneSetting_BathroomTopLoading();
        void send_write_SystemParameter_SceneSetting_LivingTopMounted();
        void send_write_SystemParameter_SceneSetting_OfficeTopLoading();
        void send_write_SystemParameter_SceneSetting_HotelTopLoading(); //hotel is the largest?

};

#endif

presence-sensor.yaml

esphome:
  name: presence-sensor
  platform: ESP32
  board: esp32dev
  includes:
    - presence_sensor.h
    - libraries/Radar.h
    - libraries/Radar.cpp    
    
logger:
  #level: VERBOSE #makes uart stream available in esphome logstream
  baud_rate: 0 #disable logging over uart

api:

ota:
  password: "password"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Presence-Sensor"
    password: "password"

captive_portal:
      
#9600 baud is mandatory, GPIOs are to your preference
uart:
  id: uart_bus
  tx_pin: GPIO26
  rx_pin: GPIO27
  baud_rate: 9600      
      

sensor:
  - platform: custom
    lambda: |-
      auto my_sensor = new PresenceSensor(id(uart_bus)); 
      App.register_component(my_sensor);
      return {my_sensor->presence_sensor};
  
    sensors:
      - name: "Presence Level"
        accuracy_decimals: 1
        state_class: measurement

Need 5V, GND, RX, TX. That’s All!

3 Likes

Oh snap thanks!

Just caught your comment. That could make a difference.
Edit: Tested w/ oscilloscope and my esp32 has a ripple of about 100mV on the edge of “<100mV” and a supply voltage of 4.75V which is also on the edge of “4.9V-6V”

2 Likes

Thank you very much!

Is this for the 60Ghz Seeed Sleep and Breathe sensor?

I can’t get it to work :frowning:

Nah, it’s for the 24Ghz one.

Same idea for the 60Ghz one but different packet header and checksum(which doesn’t matter for reading)

For the 60Ghz one, you would wait until you see the end of frame packet 0x54 0x43

void loop() override
{
    while (available() > 0)
    {
        bytes.push_back(read());

        //End of Frame is 0x54 0x43
        if(bytes[bytes.size()-2] == 0x54 && bytes[bytes.size()-1] == 0x43)
        {            
            processPacket();
            bytes.clear();        
        }
    }
}

1 Like

There’s some news!

Seeed just released a Fall Detection Pro module which uses 60Ghz and has a range of 6m!

Significantly higher accuracy than the 24GHz one and is highly configurable with a key setting - maximum range

2 Likes

This is Great. Working fine. Thanks a lot for all the help and effort from your side.
One request: Can we set the sensitivity threshold for the sensor from HomeAssistant, like using a slider or number? It would have been a handy feature.

Refer to crlogic and I’s discussion about 10 messages ago in this thread.

He has got a slider working as you describe.

I’m not so familiar with mmWave stuff. What do you think is best to use in a home to detect if a person is in a room (either sitting at a desk or sleeping in a bed) Seeed Studion “60GHz mmWave Sensor - Breathing and Heartbeat Module” or “60GHz mmWave Radar Sensor - Fall Detection Module Pro”?

I’m planing to build some of these to use it in each room in my home, but not sure which one to get.

Thank…

DFRobot Sen0395. But it’s not in stock.
Fall Module pro is untested but the spec sheet makes it look promising.

I had a few thoughts on some:

2 Likes