I am super frustrated with getting the LD2410 to work over UART on a Wemos D1 Mini 32 (ESP32-WROOM). Admittedly there is a lot going on so I was expecting to have issues with the high baud rate but even setting it at 9600, nothing…
substitutions:
devicename: "office-air-quality-sensor"
devicename_no_dashes: "office_air_quality_sensor"
friendly_devicename: "Office Air Quality Sensor"
device_description: "Air Gradient Air Quality Sensor"
update_interval_s: "2s"
update_interval_wifi: "120s"
esphome:
name: ${devicename}
comment: ${device_description}
friendly_name: ${friendly_devicename}
# Automatically add the mac address to the name
# so you can use a single firmware for all devices
# name_add_mac_suffix: true
includes:
- custom_components/ld2410/ld2410_uart.h
on_boot:
priority: 600
# ...
then:
- lambda: |-
auto uart_component = static_cast<LD2410 *>(ld2410);
uart_component->setNumbers(maxMovingDistanceRange, maxStillDistanceRange, noneDuration);
esp32:
board: wemos_d1_mini32
# board: nodemcu-32s
# framework:
# type: esp-idf
# version: recommended
# Enable logging
logger:
baud_rate: 0
# Enable Home Assistant API
api:
# password: !secret api_pwd
ota:
password: !secret ota_pwd
wifi:
networks:
- ssid: !secret iot_wifi_ssid
password: !secret iot_wifi_password
reboot_timeout: 15min
#Faster than DHCP. Also use if can't reach because of name change
manual_ip:
static_ip: 192.168.3.212
gateway: 192.168.3.1
subnet: 255.255.255.0
dns1: 192.168.1.25
dns2: 192.168.1.26
#Manually override what address to use to connect to the ESP.
#Defaults to auto-generated value. Example, if you have changed your
#static IP and want to flash OTA to the previously configured IP address.
use_address: 192.168.3.212
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "${devicename}"
password: !secret iot_wifi_password
switch:
- platform: restart
name: "Restart"
- platform: template
name: "Calibrate CO2 Sensor"
id : "calibrate_co2_sensor"
disabled_by_default: true
turn_on_action:
- senseair.background_calibration: co2_sensor
- logger.log: "CO2 Sensor Calibration Triggered! Must be done OUTDOORS!"
- platform: template
name: "CO2 Sensor Calibration Result"
id : co2_sensor_calibration_result
disabled_by_default: true
turn_on_action:
- senseair.background_calibration_result: co2_sensor
# Source: https://github.com/airgradienthq/arduino/blob/43f599a0a7d65524c49d00f546f814420aeaed6e/AirGradient.cpp#L123
- platform: template
name: "PMS5003"
id: pms_switch
optimistic: true
turn_on_action:
- uart.write:
id: pms5003_uart
data: [0x42, 0x4D, 0xE4, 0x00, 0x01, 0x01, 0x74]
turn_off_action:
- uart.write:
id: pms5003_uart
data: [0x42, 0x4D, 0xE4, 0x00, 0x00, 0x01, 0x73]
button:
- platform: safe_mode
name: "Restart (Safe Mode)"
- platform: template
name: "Reboot LD2410"
on_press:
lambda: 'static_cast<LD2410 *>(ld2410)->reboot();'
- platform: template
name: "Turn on config mode"
on_press:
- lambda: 'static_cast<LD2410 *>(ld2410)->setConfigMode(true);'
- platform: template
name: "Turn off config mode"
on_press:
- lambda: 'static_cast<LD2410 *>(ld2410)->setConfigMode(false);'
- platform: template
name: "Get config"
on_press:
- lambda: 'static_cast<LD2410 *>(ld2410)->queryParameters();'
- platform: template
name: "Set baud rate to 256000"
on_press:
- lambda: 'static_cast<LD2410 *>(ld2410)->setBaudrate(7);'
- platform: template
name: "Set baud rate to 115200"
on_press:
- lambda: 'static_cast<LD2410 *>(ld2410)->setBaudrate(5);'
- platform: template
name: "Set baud rate to 9600"
on_press:
- lambda: 'static_cast<LD2410 *>(ld2410)->setBaudrate(1);'
# Sync time with Home Assistant
time:
- platform: homeassistant
id: ha_time
text_sensor:
- platform: wifi_info
ip_address:
name: "IP"
icon: "mdi:ip-outline"
update_interval: ${update_interval_wifi}
ssid:
name: "SSID"
icon: "mdi:wifi-settings"
update_interval: ${update_interval_wifi}
bssid:
name: "BSSID"
icon: "mdi:wifi-settings"
update_interval: ${update_interval_wifi}
mac_address:
name: "MAC"
icon: "mdi:network-outline"
scan_results:
name: "Wifi Scan"
icon: "mdi:wifi-refresh"
disabled_by_default: true
#https://esphome.io/guides/automations.html?highlight=restore_value#bonus-2-global-variables
globals: ##to set default reboot behavior
# Wifi variables
- id: wifi_connection
type: bool
restore_value: no
initial_value: "false"
- id: display_on_off
type: bool
restore_value: no
initial_value: "true"
- id: page_id
type: int
restore_value: no
initial_value: "0"
- id: last_page_id
type: int
restore_value: no
initial_value: "3"
- id: max_loops
type: int
restore_value: no
initial_value: "3"
- id: display_loops_counter
type: int
restore_value: no
initial_value: "0"
- id: debug_on_off
type: bool
restore_value: no
initial_value: 'false'
i2c:
sda: 21 #D2
scl: 22 #D1
uart:
- id: pms5003_uart
rx_pin: 18 #D5
tx_pin: 19 #D6
baud_rate: 9600
- id: co2_uart
rx_pin: 16 #D4
tx_pin: 17 #D3
baud_rate: 9600
- id: uart1
rx_pin: 32
tx_pin: 33
baud_rate: 256000 # Change this according to your setting
parity: NONE
stop_bits: 1
debug:
direction: BOTH
dummy_receiver: false
after:
delimiter: [0xF8,0xF7,0xF6,0xF5]
custom_component:
- lambda: |-
return {new LD2410(id(uart1))};
components:
- id: ld2410
font:
# gfonts://family[@weight]
- file: "gfonts://Roboto"
id: roboto
size: 12
- file: "gfonts://Roboto"
id: roboto_symbols
size: 12
glyphs: [
"\U000000B5", #µ
"\U00000067" #g
]
- file: "gfonts://Roboto"
id: roboto_small
size: 12
- file: "gfonts://Roboto"
id: roboto_medium
size: 16
- file: "gfonts://Roboto"
id: roboto_large
size: 32
- file: "fonts/materialdesignicons-webfont.ttf"
id: wifi_icon_font
size: 12
glyphs: [
"\U000F05A9", #wifi
"\U000F05AA" #no wifi
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: face_icon_font
size: 48
glyphs: [
"\U000F01F5", #mdi-emoticon-happy-outline
"\U000F01F6", #mdi-emoticon-neutral-outline
"\U000F01F8" #mdi-emoticon-sad-outline
]
# https://www.co2meter.com/blogs/news/co2-levels-at-home
# ~400 ppm background (normal) outdoor air levels
# 400- 1,000 ppm typical levels found in occupied spaces with good air exchange
# 1,000 – 2,000 ppm levels associated with complaints of drowsiness and poor air
# 2,000 – 5,000 ppm levels associated with headaches, sleepiness, and stagnant, stale, stuffy air,
# poor concentration, loss of attention, increased heart rate and slight nausea may also be present
# >5,000 ppm Exposure may lead to serious oxygen deprivation symptoms
display:
- platform: ssd1306_i2c
id: device_display
model: "SH1106 128x64"
address: 0x3C
rotation: 180
flip_x: false
flip_y: false
offset_y: 0
offset_x: 0
external_vcc: true
update_interval: 1s
pages:
- id: display_auto_off_warning
lambda: |-
it.rectangle(0, 0, 128, 64);
it.printf(4, 4, id(roboto_small), "Display will turn off");
it.printf(4, 23, id(roboto_small), "automatically.");
it.printf(4, 42, id(roboto_small), "Touch logo to turn on.");
- id: page1
lambda: |-
//it.rectangle(0, 0, 128, 64);
it.printf( 6, 8, id(roboto_medium), "CO2 ");
it.printf( 92, 8, id(roboto_medium), TextAlign::TOP_RIGHT, "%5.0f", id(co2).state);
it.printf(120, 11, id(roboto), TextAlign::TOP_RIGHT, "ppm");
it.line( 0, 32, 128, 32);
it.line(64, 32, 64, 128);
it.printf( 4, 34, id(roboto), "C");
it.printf( 68, 34, id(roboto), "RH");
it.printf( 54, 40, id(roboto_medium), TextAlign::TOP_RIGHT, "%3.1f°", id(temp).state);
it.printf(120, 40, id(roboto_medium), TextAlign::TOP_RIGHT, "%2.0f%%", id(humidity).state);
- id: page2
lambda: |-
//it.rectangle(0, 0, 128, 64);
it.printf(4, 4, id(roboto_medium), "PM 1: ");
it.printf(105, 4, id(roboto_medium), TextAlign::TOP_RIGHT, "%4.0f", id(pm1_0).state);
it.printf(124, 7, id(roboto_symbols), TextAlign::TOP_RIGHT, "µg");
it.printf(4, 23, id(roboto_medium), "PM 2.5: ");
it.printf(105, 23, id(roboto_medium), TextAlign::TOP_RIGHT, "%4.0f", id(pm2_5).state);
it.printf(124, 26, id(roboto_symbols), TextAlign::TOP_RIGHT, "µg");
it.printf(4, 42, id(roboto_medium), "PM 10: ");
it.printf(105, 42, id(roboto_medium), TextAlign::TOP_RIGHT, "%4.0f", id(pm10_0).state);
it.printf(124, 45, id(roboto_symbols), TextAlign::TOP_RIGHT, "µg");
- id: page3
lambda: |-
//it.rectangle(0, 0, 128, 64);
if ((id(co2).state <= 1000.0) && (id(pm2_5).state < 35 )) {
it.printf(8, 8, id(face_icon_font), "%s", "\U000F01F5"); //mdi-emoticon-happy-outline
it.printf(76, 14, id(roboto_medium), "ALL");
it.printf(68, 34, id(roboto_medium), "GOOD");
} else if ((id(co2).state > 1000.0 && id(co2).state < 2000.0) || (id(pm2_5).state >= 35 && id(pm2_5).state <= 50)) {
it.printf(8, 8, id(face_icon_font), "%s", "\U000F01F6"); //mdi-emoticon-neutral-outline
it.printf(74, 14, id(roboto_medium), "NOT");
it.printf(68, 34, id(roboto_medium), "GOOD");
} else {
it.printf(8, 8, id(face_icon_font), "%s", "\U000F01F8"); //mdi-emoticon-sad-outline
it.printf(72, 14, id(roboto_medium), "NOT");
it.printf(68, 34, id(roboto_medium), "SAFE");
}
interval:
- interval: 10s
then:
- if:
condition:
lambda: 'return id(display_on_off) == true;'
then:
- display.page.show: !lambda |-
ESP_LOGD("DEBUG", "page_id: %d", id(page_id));
ESP_LOGD("DEBUG", "display_loops_counter: %d", id(display_loops_counter));
ESP_LOGD("DEBUG", "max_loops: %d", id(max_loops));
switch (id(page_id)) {
case 0:
return id(display_auto_off_warning);
break;
case 1:
return id(page1);
break;
case 2:
return id(page2);
break;
case 3:
return id(page3);
break;
default:
return (id(page1));
break;
}
- component.update: device_display
- lambda: |-
if(id(display_on_off)){
id(page_id) += 1;
if(id(page_id) == id(last_page_id) + 1) {
id(page_id) = 1;
id(display_loops_counter) += 1;
}
if(id(display_loops_counter) >= id(max_loops)) {
id(display_loops_counter) = 1;
id(device_display).turn_off();
id(display_on_off) = false;
ESP_LOGD("DEBUG", "Reached max loops. Display turned off.");
}
}
- interval: 20s
then:
if:
condition:
wifi.connected:
then:
- globals.set:
id: wifi_connection
value: "true"
else:
- globals.set:
id: wifi_connection
value: "false"
- interval: 150s
# Two-minute interval to extend the life span of the PMS5003 sensor
then:
- switch.turn_on: pms_switch
- delay: 30s
- switch.turn_off: pms_switch
sensor:
- platform: wifi_signal
name: "WiFi Signal"
update_interval: ${update_interval_wifi}
device_class: signal_strength
- platform: sht3xd
temperature:
id: temp
name: Temperature
humidity:
id: humidity
name: Humidity
address: 0x44
update_interval: 10s
- platform: pmsx003
type: PMSX003
uart_id: pms5003_uart
pm_1_0:
id: pm1_0
name: "Particulate <1.0µm"
pm_2_5:
id: pm2_5
name: "Particulate <2.5µm"
pm_10_0:
id: pm10_0
name: "Particulate <10.0µm"
- platform: custom
lambda: |-
auto uart_component = static_cast<LD2410 *>(ld2410);
return {uart_component->movingTargetDistance,uart_component->movingTargetEnergy,uart_component->stillTargetDistance,uart_component->stillTargetEnergy,uart_component->detectDistance};
sensors:
- name: "Moving Target Distance"
unit_of_measurement: "cm"
accuracy_decimals: 0
- name: "Moving Target Energy"
unit_of_measurement: "%"
accuracy_decimals: 0
- name: "Still Target Distance"
unit_of_measurement: "cm"
accuracy_decimals: 0
- name: "Still Target Energy"
unit_of_measurement: "%"
accuracy_decimals: 0
- name: "Detect Distance"
unit_of_measurement: "cm"
accuracy_decimals: 0
- platform: senseair
id: co2_sensor
uart_id: co2_uart
co2:
id: co2
name: "CO2"
update_interval: 60s
binary_sensor:
- platform: gpio
pin:
number: 26
inverted: true
mode:
input: true
pullup: true
name: "Left Touch Sensor"
id: touch_sensor_lx
on_press:
then:
lambda: |-
id(display_toggle).execute();
- platform: custom
lambda: |-
auto uart_component = static_cast<LD2410 *>(ld2410);
return {uart_component->hasTarget,uart_component->hasMovingTarget,uart_component->hasStillTarget,uart_component->lastCommandSuccess};
binary_sensors:
- name: "Has Target"
- name: "Has Moving Target"
- name: "Has Still Target"
- name: "Last Command Success"
- platform: gpio
pin:
number: 23
inverted: true
mode:
input: true
pullup: true
name: "Right Touch Sensor"
id: touch_sensor_rx
on_press:
then:
lambda: |-
id(display_toggle).execute();
- platform: gpio
device_class: occupancy
pin:
number: 27
inverted: false
name: "Occupancy"
filters:
- delayed_off: 1000ms
script:
- id: display_toggle
then:
- lambda: |-
if(id(display_on_off)) {
id(device_display).turn_off();
id(display_on_off) = false;
ESP_LOGD("DEBUG", "Display turned off.");
} else {
id(page_id) = 1;
id(display_loops_counter) = 0;
id(display_on_off) = true;
id(display_first_page).execute();
id(device_display).turn_on();
ESP_LOGD("DEBUG", "Display turned on.");
}
- id: display_first_page
then:
- display.page.show: page1
- component.update: device_display
number:
- platform: template
name: "Max Moving Distance Range"
id: maxMovingDistanceRange
min_value: 1
max_value: 8
step: 1
update_interval: never
optimistic: true
set_action:
- lambda: |-
auto uart_component = static_cast<LD2410 *>(ld2410);
uart_component->setMaxDistancesAndNoneDuration(x,id(maxStillDistanceRange).state,id(noneDuration).state);
- platform: template
name: "Max Still Distance Range"
id: maxStillDistanceRange
min_value: 1
max_value: 8
step: 1
update_interval: never
optimistic: true
set_action:
- lambda: |-
auto uart_component = static_cast<LD2410 *>(ld2410);
uart_component->setMaxDistancesAndNoneDuration(id(maxMovingDistanceRange).state,x,id(noneDuration).state);
- platform: template
name: "None Duration"
id: noneDuration
min_value: 0
max_value: 32767
step: 1
mode: box
update_interval: never
optimistic: true
set_action:
- lambda: |-
auto uart_component = static_cast<LD2410 *>(ld2410);
uart_component->setMaxDistancesAndNoneDuration(id(maxMovingDistanceRange).state,id(maxStillDistanceRange).state,x);
The pins I have left are the outer pins
I have tried 75, 25, 32, 33, 9, 10, and 12 in various configurations without success. The logs show nothing for the LD2410. When I press buttons, I see the name of the button pressed and a short string of hex, and then nothing else. The sensor works according to the BT app which is also what I used to change baud rates to match whatever I entered in the YAML. If all fails, I will have to just read the high/low output and configure the sensor via BT, but I would really like to have it all in ESPHome.
Device:
Any suggestions?