Skip to content

Instantly share code, notes, and snippets.

@boverby
Created September 21, 2025 19:16
Show Gist options
  • Select an option

  • Save boverby/e89b549c121146466a00fddaf0e3cc08 to your computer and use it in GitHub Desktop.

Select an option

Save boverby/e89b549c121146466a00fddaf0e3cc08 to your computer and use it in GitHub Desktop.
simple espnow example for esphome
# min_espnow.yaml
# see https://github.com/u-fire/ESPHomeComponents - capture data and auto-discovery
# see https://github.com/ChuckMash/ESPythoNOW - listens to espnow from linux/python
# see https://github.com/boverby/python_espnow_mqtt - combine those two
# see https://community.home-assistant.io/t/consultation-on-the-usage-of-espnow-in-esphome/928005/33
# see https://ncrmnt.org/2021/12/06/optimizing-esp8266-esphome-for-battery-power-and-making-an-ice-bath-thermometer-as-well/
## updates in < 1 sec == more battery life
# python_espnow_mqtt_host command: journalctl --since "1 min ago" -f -o short-precise -u python_espnow_mqtt.service
substitutions:
device: min_espnow
thismacaddr: 24:EC:4A:27:18:9C
sensor_readings_count: 20 #### note 50 resulted in boot loop
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
logger:
level: DEBUG
# Deep sleep component
deep_sleep:
run_duration: 10s # doesnt matter, will be shortcut by on_boot process
sleep_duration: 300s
wakeup_pin:
number: GPIO7
allow_other_uses: true
mode:
input: true
pulldown: true
# ESP-NOW component
espnow:
auto_add_peer: true
channel: 1
peers:
- 00:14:d1:63:8c:3c ### linux based espnow receiver (dvr)
i2c:
sda: GPIO6
scl: GPIO5
globals:
- id: sent
type: int
restore_value: no
initial_value: '0'
binary_sensor:
- platform: gpio
pin:
number: GPIO7
mode:
input: true
pulldown: true
allow_other_uses: true
name: "${device} motion 07"
id: ${device}_motion_07
icon: mdi:motion-sensor
internal : true
device_class: motion
on_press:
then:
- logger.log: "${device} GPIO07 acted"
- lambda: id(sent)++;
# Sensor configuration
sensor:
- platform: shtcx
address: 0x70
update_interval: 1s
temperature:
filters:
- lambda: return x * (9.0/5.0) + 32.0;
unit_of_measurement: "°F"
state_class: "measurement"
icon: mdi:thermometer
name: "${device} Temperature"
id: ${device}_temperature
on_value:
then:
- lambda: id(sent)++;
humidity:
name: "${device} Humidity"
id: ${device}_humidity
state_class: "measurement"
icon: mdi:water-percent
filters:
- filter_out: nan
on_value:
then:
- lambda: id(sent)++;
- platform: adc
pin: GPIO3
name: "${device} vdd"
id: "${device}_vdd"
attenuation: auto
state_class: "measurement"
icon: mdi:flash-outline
update_interval: never
filters:
- median:
window_size: ${sensor_readings_count}
send_every: ${sensor_readings_count}
send_first_at: ${sensor_readings_count}
- lambda: return x * 5.537 * 1.03759398 ;
on_value:
then:
- component.update: "${device}_battery"
- lambda: id(sent)++;
# https://community.home-assistant.io/t/esphome-battery-level-sensor/245196/19
- platform: template
name: "${device} Battery"
id: ${device}_battery
lambda: return id(${device}_vdd).state;
accuracy_decimals: 0
unit_of_measurement: "%"
device_class: battery
state_class: "measurement"
icon: mdi:battery-medium
update_interval: never
filters:
- calibrate_linear:
method: exact
datapoints:
- 0.00 -> 0.0
- 3.30 -> 1.0
- 3.39 -> 10.0
- 3.75 -> 50.0
- 4.11 -> 90.0
- 4.20 -> 100.0
- lambda: |-
if (x <= 100) {
return x;
} else {
return 100;
}
if (x <0) {
return 0;
}
on_value:
then:
- lambda: id(sent)++;
# Main automation to run on boot
esphome:
name: ${device}
platformio_options:
build_flags: -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1
on_boot:
# Manually trigger sensor updates on boot
- then:
- wait_until: ### required because shtcx is not pollable
condition:
- lambda: 'return id(${device}_temperature).has_state();'
- repeat:
count: ${sensor_readings_count}
then:
- component.update: ${device}_vdd
- espnow.send:
address: "00:14:d1:63:8c:3c"
data: !lambda |-
char buf[160];
int8_t accuracy = id(${device}_temperature)->get_accuracy_decimals();
sprintf(buf, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:",
str_snake_case( App.get_name() ).c_str() ,
id(${device}_temperature).get_device_class().c_str(),
state_class_to_string( id(${device}_temperature)->get_state_class() ) ,
str_snake_case( id(${device}_temperature).get_name() ).c_str() ,
id(${device}_temperature).get_unit_of_measurement().c_str() ,
value_accuracy_to_string(id(${device}_temperature).state, accuracy).c_str() ,
id(${device}_temperature).get_icon().c_str(),
ESPHOME_VERSION,
ESPHOME_BOARD,
"sensor"
);
std::string s = buf;
return std::vector<unsigned char>( s.begin(), s.end() );
- espnow.send:
address: "00:14:d1:63:8c:3c"
data: !lambda |-
char buf[160];
int8_t accuracy = id(${device}_humidity)->get_accuracy_decimals();
sprintf(buf, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:",
str_snake_case( App.get_name() ).c_str() ,
id(${device}_humidity).get_device_class().c_str(),
state_class_to_string( id(${device}_humidity)->get_state_class() ) ,
str_snake_case( id(${device}_humidity).get_name() ).c_str() ,
id(${device}_humidity).get_unit_of_measurement().c_str() ,
value_accuracy_to_string(id(${device}_humidity).state, accuracy).c_str() ,
id(${device}_humidity).get_icon().c_str(),
ESPHOME_VERSION,
ESPHOME_BOARD,
"sensor"
);
std::string s = buf;
return std::vector<unsigned char>( s.begin(), s.end() );
- espnow.send:
address: "00:14:d1:63:8c:3c"
data: !lambda |-
char buf[160];
int8_t accuracy = id(${device}_vdd)->get_accuracy_decimals();
sprintf(buf, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:",
str_snake_case( App.get_name() ).c_str() ,
id(${device}_vdd).get_device_class().c_str(),
state_class_to_string( id(${device}_vdd)->get_state_class() ) ,
str_snake_case( id(${device}_vdd).get_name() ).c_str() ,
id(${device}_vdd).get_unit_of_measurement().c_str() ,
value_accuracy_to_string(id(${device}_vdd).state, accuracy).c_str() ,
id(${device}_vdd).get_icon().c_str(),
ESPHOME_VERSION,
ESPHOME_BOARD,
"sensor"
);
std::string s = buf;
return std::vector<unsigned char>( s.begin(), s.end() );
on_sent:
- logger.log:
format: "ESPNow message vdd sent successfully"
level: INFO
tag: ESPNOW
on_error:
- logger.log: "ESPNow message vdd failed to send"
- espnow.send:
address: "00:14:d1:63:8c:3c"
data: !lambda |-
char buf[160];
int8_t accuracy = id(${device}_battery)->get_accuracy_decimals();
sprintf(buf, "%s:%s:%s:%s:%s:%s:%s:%s:%s:%s:",
str_snake_case( App.get_name() ).c_str() ,
id(${device}_battery).get_device_class().c_str(),
state_class_to_string( id(${device}_battery)->get_state_class() ) ,
str_snake_case( id(${device}_battery).get_name() ).c_str() ,
id(${device}_battery).get_unit_of_measurement().c_str() ,
value_accuracy_to_string(id(${device}_battery).state, accuracy).c_str() ,
id(${device}_battery).get_icon().c_str(),
ESPHOME_VERSION,
ESPHOME_BOARD,
"sensor"
);
std::string s = buf;
return std::vector<unsigned char>( s.begin(), s.end() );
on_sent:
- logger.log:
format: "ESPNow message battery sent successfully"
level: INFO
tag: ESPNOW
on_error:
- logger.log: "ESPNow message battery failed to send"
# Ensure the device has time to transmit before sleeping
# - delay: 5s
- deep_sleep.enter #### shortcut to end since all work done ( save battery)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment