Created
November 17, 2024 08:07
-
-
Save SimedruF/0c49ed4242479e0aec08a680aa5eb743 to your computer and use it in GitHub Desktop.
ESP32_hlkld2410b
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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