ld2410.tc¶
HLK-LD2410 24GHz mmWave Human Presence Sensor
// HLK-LD2410 24GHz mmWave Human Presence Sensor
// Serial: 256000 baud 8N1, continuous reporting every ~50ms
// Default wiring: ESP RX=GPIO8 ← LD2410 TX
// ESP TX=GPIO7 → LD2410 RX
// VCC=3.3V, GND=GND
//
// Reports: target state (none/moving/stationary/both),
// moving + stationary distance (cm) and energy (0-100),
// overall detection distance (cm)
//
// Frame format (normal mode, 23 bytes):
// Header: F4 F3 F2 F1
// Length: 0D 00 (13 bytes)
// Data: 02 AA state move_lo move_hi move_e
// stat_lo stat_hi stat_e det_lo det_hi 55 00
// Footer: F8 F7 F6 F5
#define LD_BAUD 256000
#define LD_SBUF 256
int ld_rxpin = 8;
int ld_txpin = 7;
int ld_serial = 0;
int ld_ok = 0;
// parsed sensor values
int ld_state = 0; // 0=none 1=moving 2=stationary 3=both
int ld_mdist = 0; // moving target distance (cm)
int ld_menrg = 0; // moving target energy (0-100)
int ld_sdist = 0; // stationary target distance (cm)
int ld_senrg = 0; // stationary target energy (0-100)
int ld_dist = 0; // detection distance (cm)
// frame parser state machine
char ld_buf[64];
int ld_fsm = 0;
int ld_idx = 0;
int ld_dlen = 0;
// web label
char ld_lbl[32];
// ---- frame decoder ----
void ld_decode() {
// buf[0..1]=length, [2]=type, [3]=0xAA head
// [4]=state, [5..6]=move_dist, [7]=move_energy
// [8..9]=stat_dist, [10]=stat_energy, [11..12]=det_dist
if (ld_buf[3] != 0xAA) return;
ld_state = ld_buf[4] & 0x03;
ld_mdist = (ld_buf[5] & 0xFF) | ((ld_buf[6] & 0xFF) << 8);
ld_menrg = ld_buf[7] & 0xFF;
ld_sdist = (ld_buf[8] & 0xFF) | ((ld_buf[9] & 0xFF) << 8);
ld_senrg = ld_buf[10] & 0xFF;
ld_dist = (ld_buf[11] & 0xFF) | ((ld_buf[12] & 0xFF) << 8);
ld_ok = 1;
}
// ---- serial reader with header sync ----
void ld_read() {
while (serialAvailable() > 0) {
int b = serialRead();
if (b < 0) break;
// find header F4 F3 F2 F1
if (ld_fsm == 0) {
if (b == 0xF4) ld_fsm = 1;
} else if (ld_fsm == 1) {
if (b == 0xF3) {
ld_fsm = 2;
} else if (b != 0xF4) {
ld_fsm = 0;
}
} else if (ld_fsm == 2) {
if (b == 0xF2) {
ld_fsm = 3;
} else if (b == 0xF4) {
ld_fsm = 1;
} else {
ld_fsm = 0;
}
} else if (ld_fsm == 3) {
if (b == 0xF1) {
ld_fsm = 4;
ld_idx = 0;
ld_dlen = 0;
} else if (b == 0xF4) {
ld_fsm = 1;
} else {
ld_fsm = 0;
}
} else {
// reading frame payload
ld_buf[ld_idx] = b;
ld_idx = ld_idx + 1;
// after 2 bytes we know the data length
if (ld_idx == 2) {
ld_dlen = (ld_buf[0] & 0xFF) | ((ld_buf[1] & 0xFF) << 8);
if (ld_dlen < 10 || ld_dlen > 50) {
ld_fsm = 0;
}
}
// complete frame: 2(len) + dlen(data) + 4(footer)
if (ld_idx >= 2 && ld_idx == ld_dlen + 6) {
// verify footer F8 F7 F6 F5
int fi = ld_dlen + 2;
if ((ld_buf[fi] & 0xFF) == 0xF8 &&
(ld_buf[fi + 1] & 0xFF) == 0xF7 &&
(ld_buf[fi + 2] & 0xFF) == 0xF6 &&
(ld_buf[fi + 3] & 0xFF) == 0xF5) {
ld_decode();
}
ld_fsm = 0;
}
// buffer overflow guard
if (ld_idx >= 60) {
ld_fsm = 0;
}
}
}
}
// ---- callbacks ----
void EverySecond() {
if (ld_serial) ld_read();
}
void WebCall() {
char vt[48];
if (!ld_serial) {
webSend("{s}LD2410{m}serial error{e}");
return;
}
if (!ld_ok) {
webSend("{s}LD2410{m}no data{e}");
return;
}
// presence state
webSend("{s}LD2410 Presence{m}");
if (ld_state == 0) {
webSend("None{e}");
} else if (ld_state == 1) {
webSend("Moving{e}");
} else if (ld_state == 2) {
webSend("Stationary{e}");
} else {
webSend("Mov+Stat{e}");
}
// moving target: distance / energy
sprintf(vt, "{s}LD2410 Moving{m}%d cm / ", ld_mdist);
webSend(vt);
sprintf(vt, "%d %{e}", ld_menrg);
webSend(vt);
// stationary target: distance / energy
sprintf(vt, "{s}LD2410 Static{m}%d cm / ", ld_sdist);
webSend(vt);
sprintf(vt, "%d %{e}", ld_senrg);
webSend(vt);
// detection distance (localized label)
LGetString(16, ld_lbl);
strcpy(vt, "{s}LD2410 ");
strcat(vt, ld_lbl);
strcat(vt, "{m}");
webSend(vt);
sprintf(vt, "%d cm{e}", ld_dist);
webSend(vt);
}
void JsonCall() {
if (!ld_ok) return;
char buf[64];
responseAppend(",\"LD2410\":{");
sprintf(buf, "\"State\":%d", ld_state);
responseAppend(buf);
sprintf(buf, ",\"MovingDist\":%d", ld_mdist);
responseAppend(buf);
sprintf(buf, ",\"MovingEnergy\":%d", ld_menrg);
responseAppend(buf);
sprintf(buf, ",\"StaticDist\":%d", ld_sdist);
responseAppend(buf);
sprintf(buf, ",\"StaticEnergy\":%d", ld_senrg);
responseAppend(buf);
sprintf(buf, ",\"Distance\":%d}", ld_dist);
responseAppend(buf);
}
void Command(char cmd[]) {
char buf[96];
if (!ld_ok) {
responseCmnd("no data");
return;
}
sprintf(buf, "State=%d", ld_state);
responseCmnd(buf);
sprintf(buf, " MovDist=%d", ld_mdist);
responseAppend(buf);
sprintf(buf, " MovE=%d", ld_menrg);
responseAppend(buf);
sprintf(buf, " StatDist=%d", ld_sdist);
responseAppend(buf);
sprintf(buf, " StatE=%d", ld_senrg);
responseAppend(buf);
sprintf(buf, " Dist=%d", ld_dist);
responseAppend(buf);
}
void WebUI() {
webPulldown(ld_rxpin, "RX Pin", "@getfreepins");
webPulldown(ld_txpin, "TX Pin", "@getfreepins");
}
void OnExit() {
serialClose();
}
int main() {
delay(10000);
// restore persisted pin config
webPulldown(ld_rxpin, "RX Pin", "@getfreepins");
webPulldown(ld_txpin, "TX Pin", "@getfreepins");
int ret = serialBegin(ld_rxpin, ld_txpin, LD_BAUD, 3, LD_SBUF);
if (ret == 1) {
ld_serial = 1;
}
addCommand("Radar");
return 0;
}