Skip to content

Instantly share code, notes, and snippets.

@SimedruF
Created November 17, 2024 08:07
Show Gist options
  • Save SimedruF/0c49ed4242479e0aec08a680aa5eb743 to your computer and use it in GitHub Desktop.
Save SimedruF/0c49ed4242479e0aec08a680aa5eb743 to your computer and use it in GitHub Desktop.
ESP32_hlkld2410b
/*
Florin Simedru
Complete project details at https://blog.automatic-house.ro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
/*
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
upload_port = COM3
monitor_port = COM3
monitor_speed = 115200
lib_deps =
ncmreynolds/ld2410@^0.1.4
knolleary/PubSubClient@^2.8
ottowinter/ESPAsyncWebServer-esphome@^3.1.0
bblanchon/ArduinoJson@^6.18.5
*/
#include <WiFi.h>
#include <PubSubClient.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#define MONITOR_SERIAL Serial
#define RADAR_SERIAL Serial1
#define RADAR_RX_PIN 32
#define RADAR_TX_PIN 33
#define BUILDIN_LED 2
#define PRODUCT "AHS Human presence sensor"
#define MQTT_MANUFACTURER "AHS"
#include <ld2410.h>
ld2410 radar;
uint32_t lastReading = 0;
bool radarConnected = false;
const char *ssid = "ssid";
const char *password = "password";
// Configurations MQTT
// MQTT Broker
const char *mqtt_server = "192.168.x.x";
const char *mqtt_username = "mqtt user";
const char *mqtt_password = "mqtt password";
const char *mqtt_topic_sensor_name = "hlkld2410b";
const char *mqtt_topic_p = "home/binary_sensor/hlkld2410b/hlkld2410b_presence";
const char *mqtt_topic_mt = "home/binary_sensor/hlkld2410b/hlkld2410b_mt";
const char *mqtt_topic_st = "home/binary_sensor/hlkld2410b/hlkld2410b_st";
// MD5 of chip ID. If you only have a handful of thermometers and use
// your own MQTT broker (instead of iot.eclips.org) you may want to
// truncate the MD5 by changing the 32 to a smaller value.
char machineId[32 + 1] = "";
char ha_name[32 + 1] = "Human"; // Make sure the machineId fits.
const char *workgroup = "";
// Wifi and mqtt
WiFiClient espClient;
PubSubClient mqtt_client(espClient);
AsyncWebServer server(80);
// Radar data
String radarData = "";
int stationaryTargetDistance = 0;
int movingTargetDistance = 0;
bool target_detected = false;
long lastMsg = 0;
bool publishSensorDiscovery(const char *sensor_name,
const char *component,
const char *config_key,
const char *device_class,
const char *name_suffix,
const char *state_topic,
const char *unit,
const char *value_template)
{
static char topic[48 + sizeof(machineId)];
snprintf(topic, sizeof(topic),
"homeassistant/%s/%s/%s/config", component, sensor_name, config_key);
DynamicJsonDocument json(1024);
if (device_class)
json["device_class"] = device_class;
json["name"] = name_suffix;
json["retain"] = true;
json["unique_id"] = String("AHS-") + config_key;
json["state_topic"] = state_topic;
if (unit)
json["unit_of_measurement"] = unit;
json["value_template"] = value_template;
json["payload_on"] = true;
json["payload_off"] = false;
json["availability_topic"] = "homeassistant/status";
json["device"]["identifiers"] = ESP.getSketchMD5();
json["device"]["manufacturer"] = MQTT_MANUFACTURER;
json["device"]["model"] = PRODUCT;
json["device"]["name"] = String(ha_name) + " " + name_suffix;
json["device"]["sw_version"] = "0.0.1";
JsonArray connections = json["device"].createNestedArray("connections").createNestedArray();
connections.add("mac");
connections.add(WiFi.macAddress());
#ifdef SERIAL_DEBUG
Serial.print("Home Assistant discovery topic: ");
Serial.println(topic);
#endif
int payload_len = measureJson(json);
if (!mqtt_client.beginPublish(topic, payload_len, true))
{
Serial.println("beginPublish failed!\n");
return false;
}
if (serializeJson(json, mqtt_client) != payload_len)
{
Serial.println("writing payload: wrong size!\n");
return false;
}
if (!mqtt_client.endPublish())
{
Serial.println("endPublish failed!\n");
return false;
}
return true;
}
void reconnect()
{
String client_id = "esp32-client-";
// Loop until we're reconnected
while (!mqtt_client.connected())
{
client_id += String(WiFi.macAddress());
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password))
{
Serial.println("connected");
// Subscribe
mqtt_client.subscribe("esp32/output");
mqtt_client.subscribe(mqtt_topic_p);
mqtt_client.subscribe(mqtt_topic_mt);
mqtt_client.subscribe(mqtt_topic_st);
break;
}
else
{
Serial.print("failed, rc=");
Serial.print(mqtt_client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
// publishToMQTT functions
void publishToMQTT(String data)
{
String HA_data;
if (!mqtt_client.connected())
{
reconnect();
}
publishSensorDiscovery("hlkld2410b",
"binary_sensor",
"hlkld2410b_mt",
"movingTargetDistance",
"Moving Target Distance",
"home/binary_sensor/hlkld2410b/hlkld2410b_mt",
"cm",
"{{ value_json.movingTargetDistance }}");
publishSensorDiscovery("hlkld2410b",
"binary_sensor",
"hlkld2410b_st",
"stationaryTargetDistance",
"Stationar Target Distance",
"home/binary_sensor/hlkld2410b/hlkld2410b_st",
"cm",
"{{ value_json.stationaryTargetDistance }}");
publishSensorDiscovery("hlkld2410b",
"binary_sensor",
"hlkld2410b_presence",
"motion",
"Human presence",
"home/binary_sensor/hlkld2410b/hlkld2410b_presence",
"presence",
"{{ value_json[presence] }}");
HA_data = "{";
HA_data += "\"movingTargetDistance\":" + String(movingTargetDistance);
HA_data += "}";
mqtt_client.publish(mqtt_topic_mt, HA_data.c_str());
HA_data = "{";
HA_data += "\"stationaryTargetDistance\":" + String(stationaryTargetDistance);
HA_data += "}";
mqtt_client.publish(mqtt_topic_st, HA_data.c_str());
HA_data = "{";
if (target_detected == true)
HA_data += "\"presence\":" + String("true");
else
HA_data += "\"presence\":" + String("false");
HA_data += "}";
mqtt_client.publish(mqtt_topic_p, HA_data.c_str());
}
// Calling of publish to MQTT
void readRadarData(HardwareSerial &radarSerial)
{
publishToMQTT(radarData);
}
void setup_connections()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("Conecting to WiFi...");
}
Serial.println("WiFi conectected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
mqtt_client.setServer(mqtt_server, 1883);
// web server config
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{
String html_str = R"(
<html >
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<title>Radar Data</title>
<style>
.charts-container {
display: flex;
justify-content: space-around; /* Spațiere între div-uri */
align-items: flex-start; /* Aliniere pe verticală */
flex-wrap: wrap; /* Trecere pe rând dacă spațiul e insuficient */
gap: 20px; /* Distanță între div-uri */
margin: 20px auto;
max-width: 90%; /* Limitarea lățimii containerului */
/* max-height: 90%; Limitarea lățimii containerului */
}
.chart-wrapper {
/* flex: 1; Ajustare automată a dimensiunii */
min-width: 700px; /* Dimensiune minimă pentru fiecare div */
max-width: 700px; /* Dimensiune maximă */
text-align: center; /* Text centrat (ex. titluri) */
}
/* Stil pentru Radar Chart */
.radar-canvas {
width: 100%;
height: 700px;
border: 2px solid #4caf50; /* Verde pentru radar */
background-color: #ffffff; /* Fundal deschis */
border-radius: 8px;
}
/* Stil pentru Boolean Chart */
.boolean-canvas {
width: 100%;
height: 700px;
border: 2px solid #2196f3; /* Albastru pentru boolean */
background-color: #ffffff; /* Fundal deschis */
border-radius: 8px;
}
.radar-canvas:hover {
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.5);
}
.boolean-canvas:hover {
box-shadow: 0 4px 8px rgba(33, 150, 243, 0.5);
}
</style>
</head>
<body>
<h1>Radar Data</h1>
<div class="charts-container">
<div class="chart-wrapper">
<h2>Radar Chart</h2>
<canvas id="radarChart" class="radar-canvas"></canvas>
</div>
<div class="chart-wrapper">
<h2>Target detection</h2>
<canvas id="booleanChart" class="boolean-canvas"></canvas>
</div>
</div>
<script>
const ctx = document.getElementById('radarChart').getContext('2d');
const radarChart = new Chart(ctx, {
type: 'line',
data: {
labels: [], // Timp
datasets: [{
label: 'Moving Target Distance',
data: [],
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
fill: false
},
{
label: 'Stationary Target Distance',
data: [],
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 2,
fill: false
}]
},
options: {
scales: {
x: {
title: {
display: true,
text: 'Time'
}
},
y: {
title: {
display: true,
text: 'Value'
}
}
}
}
});
// Boolean Chart pentru valoarea binară
const booleanCtx = document.getElementById('booleanChart').getContext('2d');
const booleanChart = new Chart(booleanCtx, {
type: 'bar',
data: {
labels: [],
datasets: [{
label: 'Target detection',
data: [],
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
x: { title: { display: true, text: 'Time' } },
y: {
title: { display: true, text: 'State' },
min: 0, // Limita inferioară
max: 1, // Limita superioară
ticks: {
stepSize: 1 // Salturi între valorile afișate pe axă
}
}
}
}
});
// Funcție pentru actualizarea graficelor
async function fetchRadarData() {
try {
const response = await fetch('/data');
const data = await response.json();
const time = new Date().toLocaleTimeString();
// Actualizare Radar Chart
radarChart.data.labels.push(time);
radarChart.data.datasets[0].data.push(data.value1);
radarChart.data.datasets[1].data.push(data.value2);
if (radarChart.data.labels.length > 50) {
radarChart.data.labels.shift();
radarChart.data.datasets[0].data.shift();
radarChart.data.datasets[1].data.shift();
}
radarChart.update();
// Actualizare Boolean Chart
booleanChart.data.labels.push(time);
booleanChart.data.datasets[0].data.push(data.binary);
if (booleanChart.data.labels.length > 50) {
booleanChart.data.labels.shift();
booleanChart.data.datasets[0].data.shift();
}
booleanChart.update();
} catch (error) {
console.error('Eroare la preluarea datelor:', error);
}
}
setInterval(fetchRadarData, 1000); // Actualizare la fiecare secundă
</script>
</body>
</html>)";
request->send(200, "text/html", html_str); });
// Endpoint for radar data
server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request)
{
// String json = "{\"value\":" + String(radarValue) + "}";
String json = "{\"value1\":" + String(movingTargetDistance) +
",\"value2\":" + String(stationaryTargetDistance) +
+", \"binary\":" + String(target_detected) + "}";
request->send(200, "application/json", json); });
server.begin();
reconnect();
}
void setup(void)
{
MONITOR_SERIAL.begin(115200); // Feedback over Serial Monitor
setup_connections();
// radar.debug(MONITOR_SERIAL); //Uncomment to show debug information from the library on the Serial Monitor. By default this does not show sensor reads as they are very frequent.
#if defined(ESP32)
RADAR_SERIAL.begin(256000, SERIAL_8N1, RADAR_RX_PIN, RADAR_TX_PIN); // UART for monitoring the radar
#elif defined(__AVR_ATmega32U4__)
RADAR_SERIAL.begin(256000); // UART for monitoring the radar
#endif
delay(500);
MONITOR_SERIAL.print(F("\nConnect LD2410 radar TX to GPIO:"));
MONITOR_SERIAL.println(RADAR_RX_PIN);
MONITOR_SERIAL.print(F("Connect LD2410 radar RX to GPIO:"));
MONITOR_SERIAL.println(RADAR_TX_PIN);
MONITOR_SERIAL.print(F("LD2410 radar sensor initialising: "));
if (radar.begin(RADAR_SERIAL))
{
MONITOR_SERIAL.println(F("OK"));
MONITOR_SERIAL.print(F("LD2410 firmware version: "));
MONITOR_SERIAL.print(radar.firmware_major_version);
MONITOR_SERIAL.print('.');
MONITOR_SERIAL.print(radar.firmware_minor_version);
MONITOR_SERIAL.print('.');
MONITOR_SERIAL.println(radar.firmware_bugfix_version, HEX);
}
else
{
MONITOR_SERIAL.println(F("not connected"));
}
// Configure the LED from the board
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
radar.read();
if (radar.isConnected() && millis() - lastReading > 1000) // Report every 1000ms
{
lastReading = millis();
if (radar.presenceDetected())
{
if (radar.stationaryTargetDetected())
{
Serial.print(F("Stationary target: "));
Serial.print(radar.stationaryTargetDistance());
Serial.print(F("cm energy:"));
Serial.print(radar.stationaryTargetEnergy());
Serial.print(' ');
radarData = "Stationary target: " + String(radar.stationaryTargetDistance(), DEC) + " cm energy:" + String(radar.stationaryTargetEnergy(), DEC);
stationaryTargetDistance = radar.stationaryTargetDistance();
}
if (radar.movingTargetDetected())
{
Serial.print(F("Moving target: "));
Serial.print(radar.movingTargetDistance());
Serial.print(F("cm energy:"));
Serial.print(radar.movingTargetEnergy());
radarData = "Moving target: " + String(radar.movingTargetDistance(), DEC) + " cm energy:" + String(radar.movingTargetEnergy(), DEC);
movingTargetDistance = radar.movingTargetDistance();
}
Serial.println();
digitalWrite(BUILDIN_LED, HIGH); // turn the LED on (HIGH is the voltage level)
target_detected = true;
}
else
{
Serial.println(F("No target"));
radarData = "No target ";
digitalWrite(BUILDIN_LED, LOW); // turn the LED off by making the voltage LOW
target_detected = false;
movingTargetDistance = 0;
stationaryTargetDistance = 0;
}
}
mqtt_client.loop();
long now = millis();
if (now - lastMsg > 5000)
{
lastMsg = now;
readRadarData(Serial);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment