mmWave human presence for under $20?!

Just guessing, but one is better at short range, the other at far range.

They would probably be interchangeable for a lot of applications.

I was only able to get my hands on the human static one which is also cheaper. (Arriving today!)


Looking forward for your review, I’m just concerned about the wall penetration if it still detects person from different rooms.

I think it should be able to detect through walls, but you can set up the distance you want

I read a bit more and looks like the breath one can detect when someone is getting closer or further.

Let us know if the static can do that too as I can only order the expensive one!

1 Like

I’m super curious to know how these work with multiple people in the room!

Or… pets?

So the good news is both the wifi & zigbee version of the tuya mm wave human detector version i ordered arrived today.
The bad news, i might have fried the zigbee version.
I wired up to a plug & plugged into a socket extension to test it out (which is what i’ve been doing normally for relay switches). I turned it on, noticed some sparks and i immediately turned it off.

I’m no electrician, so could anyone explain what went wrong and how can i fix that before i test the wifi version? My house is on 240v btw

If that manual says 12 volts (assuming DC) and you hooked it up to 230 AC then it’s beyond dead.


Thanks, so that means i need to get power supply converter first (ac 240v - 12v)
Looks like the wifi version does include it, but not the zigbee one

Another lesson learned for me i guess :sweat_smile:

Labelling 12v DC connections “L” and “N” :roll_eyes:

1 Like

Yeah… that’s a bit silly.
But I have learned to expect nothing when it comes to manuals these days.

So I’ve been playing with the Seeed human presence detection sensor for two days now. I have improved their crappy library a bit by adding things like writing commands over serial with the necessary checksum code.

Serial Data: it’s pretty simple to work with, and the chip sends 12~ byte packets. What each packet means is well documented for all 4 sensors. The 3 24GHz sensors are all very similar and can detect presence and movement amount and all send back identical data packets. The difference between the 3 is that:

24GHz Fall Detection: Has an extra fall detection packet it will send. This may mean it is better at detecting if someone is moving away from the sensor. More or less the same as the human presence sensor.

24GHz Sleep Detection: It is more sensitive so it can detect breath rate. It has a shorter range. I believe based on the breath rate (it tries to) detect how deep the sleep is and what the quality of sleep is. It has a heart rate packet but somehow I doubt it would be accurate. Says it has a 3m presence detection range which is the same as the other two. For motion, it could easily be more.

24GHz Human Presence / Fall Detection Range: 12m range, 5m micromotion, 3m presence range (similar to the other two for static presence range).

60GHz Sleep Detection is a different beast: Double+ the price. Totally different packet format and might need some kind of uart to serial converter. Way more things it tries to detect: breath rate, breath waveform, heart rate, number of people in the environment, “Resting Distance” - perhaps how far the person is. None of the others do. Says it has a 2m range. For motion, it could easily be more. 60GHz does not penetrate objects and walls as much as 24GHz.

Movement and direction: The documentation says you can measure whether someone is “nearby”, “walking away”, or “moving without direction” for all 4 sensors. None of them have a “moving closer” packet documented, maybe the “nearby” packet is moving closer but it really just sounded like a “person is nearby” packet. This data is too random in the Human Detection sensor to be useful. It is maybe better for the fall detection one but I’m skeptical. The 24GHz and 60GHz sleep ones seem to have a better perception of distance and movement direction so they may be able to better detect if someone is walking away. No promises!

My opinion on the human presence sensor:

I’m happy with it. This sensor definitely has a better processing algorithm that is much more consistent and removes the need for averaging data over 20+ seconds compared to the DFRobot one. It will hold a steady, unoccupied state when there is no one around and quickly switch to occupied when someone enters the vicinity. Takes a couple of minutes to switch back to unoccupied. Seems to be a little quicker when you reduce the sensitivity.

Sensitivity setting: All 3 have a sensitivity setting (1-10) which I was able to write to the board over serial (Checksum required!). I reduced the sensitivity from the default 7 down to 3.

"Motor Signs": You can get a 4 byte float back from 0-100 (called motor signs). 0 is unoccupied, 1 is occupied but stationary, and 1-100 is increasing amounts of movement in the area. The number does seem to reflect the amount of movement. If it is >1 I would do a moving average to get a clearer picture of how much movement there is. You need to write a packet to start getting this data back frequently.

This is in reference to @mattdm 's question:
What the sensor does is it sends out radar waves and tries to interpret them as they return. If you have multiple people or dogs in the area it will all just affect the readings you get.

Dog walks past a little ways away? If you have it set to low sensitivity maybe it wouldn’t interpret it as occupied.
Multiple people in the frame? If one is sitting close by and another is moving further away it would still read it as movement. Maybe 40/100 or something. Unlike the other 3, the 60GHz sleep sensor claims it can detect how many people are in the frame.

It’s a slightly smaller pin header than Arduino / most breadboards.

Another person got the human presence one working:

Looks somewhat similar to how I have implemented it.

Happy to answer any questions!


Hi, if i point the sensor towards my bed, would it still penetrate the walls next to my living room and detect the people there? Or do you still need to fine tune the settings for it


Wow. This is awesome and terrifying at the same time. Look at how small the thing is…

The sensor is fairly directional so I wouldn’t be worried about it picking up stuff from the living room based on your diagram, but I’m not sure if it would pick you up the whole night if the sensor is positioned at a similar level as you and you sleep on your back / front.

I’ll test it tonight to see if it picks me up all night if it’s placed the same as in your diagram.

This is approximately the sensor’s pickup pattern where it switches from unoccupied to occupied if I’m moving a little (same for the other two as well probs)

But the further out to the side you are, the weaker the signal and the less it goes through walls.

The vertical range is not nearly as tall as it is wide


For home automation, that seems like the killer feature.

Two people right beside eachother might still be read as 1. If they’re behind eachother especially. So I wouldn’t hold my breath for that one.

Still definitely better than regular motion detectors!

So I wouldn’t hold my breath for that one.

In that case it would stop detecting anyway :wink:


Yeah… I wouldn’t expect it to be perfectly accurate. I’m imagining that on the ceiling would provide the most accuracy in most rooms — if it can figure out “people” in that configuration.

Thanks for the update and the link to the working implementation. It works well.
Could you please tell us a bit about how to set the sensor’s sensitivity?

Here’s how to do checksums & send messages to the three boards. They all use the same checksum codes / algorithm and these commands should work on the human presence sensor, the fall detection and the 24ghz sleep one.

Here is the code

Example of how you can set up the serial connection if you set it up in C:
(9600/8N1 is standard for serial).

HardwareSerial hardwareSerial(1);

void setup() {
  hardwareSerial.begin(9600, SERIAL_8N1, 27, 26);

  while (!hardwareSerial) { //optional depending on use case
    delay(10); // wait till serial port opens


But in ESP home there is an easy way it seems UART Bus — ESPHome. You would define your uart and have a customer UARTComponent as described here Custom UART Device — ESPHome. Then you can replace the hardwareSerial.write(dataWithChecksum[n]); line inside of sendMessage with just write(dataWithChecksum[n]) using the arduino API as shown in the second link.

  id: uart_bus
  tx_pin: GPIO16 #or other. D26, etc.
  rx_pin: GPIO17 #or other. D27, etc.
  baud_rate: 9600

- lambda: |-
    auto my_custom = new MyCustomComponent(id(uart_bus));
    return {my_custom};

The setting transmission & checksum code:

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;
    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;

void 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++){      

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 send_read_RadarInfo_EnvironmentalStatus() { sendMessage(read_RadarInfo_EnvironmentalStatus, 3); };
void send_read_RadarInfo_MotorSigns() { sendMessage(read_RadarInfo_MotorSigns, 3); };
void send_read_SystemParameter_ThresholdGear() { sendMessage(read_SystemParameter_ThresholdGear, 3); };
void send_read_SystemParameter_SceneSetting() { sendMessage(read_SystemParameter_SceneSetting, 3); };

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