From e565e292888ee911cc4811f153c396ba68913d87 Mon Sep 17 00:00:00 2001
From: Aiden
Date: Sat, 25 Jul 2026 17:36:28 +0800
Subject: [PATCH] Add espnow switch
---
athom-rgbcw-bulb.yaml | 170 ++++++++++++++++++++++++++++++++---------
athom-rgbcw-light.yaml | 170 ++++++++++++++++++++++++++++++++---------
2 files changed, 264 insertions(+), 76 deletions(-)
diff --git a/athom-rgbcw-bulb.yaml b/athom-rgbcw-bulb.yaml
index 2e7ce8c..4b4b336 100644
--- a/athom-rgbcw-bulb.yaml
+++ b/athom-rgbcw-bulb.yaml
@@ -10,7 +10,7 @@ substitutions:
# Project Name
project_name: "China Athom Technology.Athom RGBCW Bulb"
# Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
- project_version: "v3.2.4"
+ project_version: "v3.2.5"
# Restore the light (GPO switch) upon reboot to state:
light_restore_mode: RESTORE_DEFAULT_ON
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
@@ -77,6 +77,11 @@ globals:
restore_value: yes
initial_value: "false"
+ - id: espnow_enabled
+ type: bool
+ restore_value: yes
+ initial_value: "true"
+
select:
- platform: template
name: "Power On State"
@@ -134,24 +139,48 @@ esphome:
then:
- if:
condition:
- lambda: 'return id(remote_paired);'
+ lambda: 'return id(espnow_enabled);'
then:
- - espnow.peer.add:
- id: espnow_component
- address: !lambda |-
- return id(remote_mac);
- - lambda: |-
- char mac[18];
- snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
- id(remote_mac)[0], id(remote_mac)[1],
- id(remote_mac)[2], id(remote_mac)[3],
- id(remote_mac)[4], id(remote_mac)[5]);
- id(remote_paired_text).publish_state(mac);
- id(remote_pairing_status).publish_state("Paired");
+ # ESP-NOW is configured with enable_on_boot: false so that the saved
+ # switch state decides whether the stack comes up at all. enable()
+ # must run before any peer.add - add_peer() returns ESP_ERR_ESPNOW_NOT_INIT
+ # while the component is disabled.
+ - lambda: 'id(espnow_component)->enable();'
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.add:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: |-
+ char mac[18];
+ snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
+ id(remote_mac)[0], id(remote_mac)[1],
+ id(remote_mac)[2], id(remote_mac)[3],
+ id(remote_mac)[4], id(remote_mac)[5]);
+ id(remote_paired_text).publish_state(mac);
+ id(remote_pairing_status).publish_state("Paired");
+ else:
+ - lambda: |-
+ id(remote_paired_text).publish_state("None");
+ id(remote_pairing_status).publish_state("Idle");
else:
+ # ESP-NOW switched off: leave the stack down, but still surface the
+ # retained pairing so the user can see it is remembered.
- lambda: |-
- id(remote_paired_text).publish_state("None");
- id(remote_pairing_status).publish_state("Idle");
+ if (id(remote_paired)) {
+ char mac[18];
+ snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
+ id(remote_mac)[0], id(remote_mac)[1],
+ id(remote_mac)[2], id(remote_mac)[3],
+ id(remote_mac)[4], id(remote_mac)[5]);
+ id(remote_paired_text).publish_state(mac);
+ } else {
+ id(remote_paired_text).publish_state("None");
+ }
+ id(remote_pairing_status).publish_state("Disabled");
- if:
condition:
and:
@@ -255,7 +284,9 @@ esp32_improv:
espnow:
id: espnow_component
auto_add_peer: false
- enable_on_boot: true
+ # Startup is driven by the "ESP-NOW" switch via the on_boot automation above,
+ # so the stack is never brought up only to be torn down again.
+ enable_on_boot: false
on_unknown_peer:
- lambda: |-
const char *const TAG = "espnow_remote";
@@ -410,6 +441,53 @@ switch:
- esp32_ble_tracker.stop_scan:
- lambda: 'ESP_LOGI("bluetooth_proxy_switch", "Bluetooth proxy scanning disabled");'
+ - platform: template
+ name: "ESP-NOW"
+ id: espnow_switch
+ icon: mdi:remote
+ entity_category: config
+ restore_mode: DISABLED
+ lambda: 'return id(espnow_enabled);'
+ turn_on_action:
+ - globals.set:
+ id: espnow_enabled
+ value: "true"
+ # enable() first: add_peer() is rejected with ESP_ERR_ESPNOW_NOT_INIT while disabled.
+ - lambda: 'id(espnow_component)->enable();'
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.add:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: 'id(remote_pairing_status).publish_state("Paired");'
+ else:
+ - lambda: 'id(remote_pairing_status).publish_state("Idle");'
+ - lambda: 'ESP_LOGI("espnow_remote", "ESP-NOW enabled");'
+ turn_off_action:
+ - globals.set:
+ id: espnow_enabled
+ value: "false"
+ # Close any open pairing window so it cannot fire once ESP-NOW comes back.
+ - lambda: 'id(remote_pairing) = false;'
+ # Drop the peer while the stack is still up (del_peer() is rejected once disabled).
+ # The pairing itself lives in remote_mac/remote_paired and is retained, so turning
+ # the switch back on re-adds the same remote without re-pairing.
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.delete:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: |-
+ id(espnow_component)->disable();
+ id(remote_pairing_status).publish_state("Disabled");
+ ESP_LOGI("espnow_remote", "ESP-NOW disabled");
+
binary_sensor:
- platform: status
name: "Status"
@@ -468,12 +546,20 @@ button:
entity_category: config
on_press:
then:
- - lambda: |-
- id(remote_pairing) = true;
- id(remote_pair_deadline) = millis() + 60000;
- id(remote_pairing_status).publish_state("Pairing for 60 seconds");
- ESP_LOGI("espnow_remote", "Pairing enabled for 60 seconds; press a ESP-NOW remote button");
- - script.execute: remote_pairing_timeout
+ - if:
+ condition:
+ lambda: 'return !id(espnow_enabled);'
+ then:
+ - lambda: |-
+ id(remote_pairing_status).publish_state("Disabled");
+ ESP_LOGW("espnow_remote", "ESP-NOW is disabled; turn on the ESP-NOW switch before pairing");
+ else:
+ - lambda: |-
+ id(remote_pairing) = true;
+ id(remote_pair_deadline) = millis() + 60000;
+ id(remote_pairing_status).publish_state("Pairing for 60 seconds");
+ ESP_LOGI("espnow_remote", "Pairing enabled for 60 seconds; press a ESP-NOW remote button");
+ - script.execute: remote_pairing_timeout
- platform: template
name: "Clear ESP-NOW Remote"
@@ -483,20 +569,28 @@ button:
then:
- if:
condition:
- lambda: 'return id(remote_paired);'
+ lambda: 'return !id(espnow_enabled);'
then:
- - espnow.peer.delete:
- id: espnow_component
- address: !lambda |-
- return id(remote_mac);
- - lambda: |-
- id(remote_mac) = std::array{0, 0, 0, 0, 0, 0};
- id(remote_paired) = false;
- id(remote_pairing) = false;
- id(remote_last_seq) = 0xFFFFFFFF;
- id(remote_paired_text).publish_state("None");
- id(remote_pairing_status).publish_state("Idle");
- ESP_LOGI("espnow_remote", "Cleared paired ESP-NOW remote");
+ - lambda: |-
+ id(remote_pairing_status).publish_state("Disabled");
+ ESP_LOGW("espnow_remote", "ESP-NOW is disabled; turn on the ESP-NOW switch before clearing the remote");
+ else:
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.delete:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: |-
+ id(remote_mac) = std::array{0, 0, 0, 0, 0, 0};
+ id(remote_paired) = false;
+ id(remote_pairing) = false;
+ id(remote_last_seq) = 0xFFFFFFFF;
+ id(remote_paired_text).publish_state("None");
+ id(remote_pairing_status).publish_state("Idle");
+ ESP_LOGI("espnow_remote", "Cleared paired ESP-NOW remote");
output:
- platform: ledc
@@ -522,13 +616,13 @@ output:
frequency: 9765
pin: GPIO6
min_power: 0
- max_power: 0.9
+ max_power: 0.95
- platform: ledc
id: warm_white_output
frequency: 9765
pin: GPIO7
min_power: 0
- max_power: 0.9
+ max_power: 0.95
light:
- platform: rgbww
diff --git a/athom-rgbcw-light.yaml b/athom-rgbcw-light.yaml
index 1180940..0b1c71a 100644
--- a/athom-rgbcw-light.yaml
+++ b/athom-rgbcw-light.yaml
@@ -10,7 +10,7 @@ substitutions:
# Project Name
project_name: "China Athom Technology.Athom 12W RGBCW Bulb"
# Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
- project_version: "v3.1.6"
+ project_version: "v3.1.7"
# Restore the light (GPO switch) upon reboot to state:
light_restore_mode: RESTORE_DEFAULT_ON
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
@@ -77,6 +77,11 @@ globals:
restore_value: yes
initial_value: "false"
+ - id: espnow_enabled
+ type: bool
+ restore_value: yes
+ initial_value: "true"
+
select:
- platform: template
name: "Power On State"
@@ -134,24 +139,48 @@ esphome:
then:
- if:
condition:
- lambda: 'return id(remote_paired);'
+ lambda: 'return id(espnow_enabled);'
then:
- - espnow.peer.add:
- id: espnow_component
- address: !lambda |-
- return id(remote_mac);
- - lambda: |-
- char mac[18];
- snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
- id(remote_mac)[0], id(remote_mac)[1],
- id(remote_mac)[2], id(remote_mac)[3],
- id(remote_mac)[4], id(remote_mac)[5]);
- id(remote_paired_text).publish_state(mac);
- id(remote_pairing_status).publish_state("Paired");
+ # ESP-NOW is configured with enable_on_boot: false so that the saved
+ # switch state decides whether the stack comes up at all. enable()
+ # must run before any peer.add - add_peer() returns ESP_ERR_ESPNOW_NOT_INIT
+ # while the component is disabled.
+ - lambda: 'id(espnow_component)->enable();'
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.add:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: |-
+ char mac[18];
+ snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
+ id(remote_mac)[0], id(remote_mac)[1],
+ id(remote_mac)[2], id(remote_mac)[3],
+ id(remote_mac)[4], id(remote_mac)[5]);
+ id(remote_paired_text).publish_state(mac);
+ id(remote_pairing_status).publish_state("Paired");
+ else:
+ - lambda: |-
+ id(remote_paired_text).publish_state("None");
+ id(remote_pairing_status).publish_state("Idle");
else:
+ # ESP-NOW switched off: leave the stack down, but still surface the
+ # retained pairing so the user can see it is remembered.
- lambda: |-
- id(remote_paired_text).publish_state("None");
- id(remote_pairing_status).publish_state("Idle");
+ if (id(remote_paired)) {
+ char mac[18];
+ snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X",
+ id(remote_mac)[0], id(remote_mac)[1],
+ id(remote_mac)[2], id(remote_mac)[3],
+ id(remote_mac)[4], id(remote_mac)[5]);
+ id(remote_paired_text).publish_state(mac);
+ } else {
+ id(remote_paired_text).publish_state("None");
+ }
+ id(remote_pairing_status).publish_state("Disabled");
- if:
condition:
and:
@@ -255,7 +284,9 @@ esp32_improv:
espnow:
id: espnow_component
auto_add_peer: false
- enable_on_boot: true
+ # Startup is driven by the "ESP-NOW" switch via the on_boot automation above,
+ # so the stack is never brought up only to be torn down again.
+ enable_on_boot: false
on_unknown_peer:
- lambda: |-
const char *const TAG = "espnow_remote";
@@ -410,6 +441,53 @@ switch:
- esp32_ble_tracker.stop_scan:
- lambda: 'ESP_LOGI("bluetooth_proxy_switch", "Bluetooth proxy scanning disabled");'
+ - platform: template
+ name: "ESP-NOW"
+ id: espnow_switch
+ icon: mdi:remote
+ entity_category: config
+ restore_mode: DISABLED
+ lambda: 'return id(espnow_enabled);'
+ turn_on_action:
+ - globals.set:
+ id: espnow_enabled
+ value: "true"
+ # enable() first: add_peer() is rejected with ESP_ERR_ESPNOW_NOT_INIT while disabled.
+ - lambda: 'id(espnow_component)->enable();'
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.add:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: 'id(remote_pairing_status).publish_state("Paired");'
+ else:
+ - lambda: 'id(remote_pairing_status).publish_state("Idle");'
+ - lambda: 'ESP_LOGI("espnow_remote", "ESP-NOW enabled");'
+ turn_off_action:
+ - globals.set:
+ id: espnow_enabled
+ value: "false"
+ # Close any open pairing window so it cannot fire once ESP-NOW comes back.
+ - lambda: 'id(remote_pairing) = false;'
+ # Drop the peer while the stack is still up (del_peer() is rejected once disabled).
+ # The pairing itself lives in remote_mac/remote_paired and is retained, so turning
+ # the switch back on re-adds the same remote without re-pairing.
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.delete:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: |-
+ id(espnow_component)->disable();
+ id(remote_pairing_status).publish_state("Disabled");
+ ESP_LOGI("espnow_remote", "ESP-NOW disabled");
+
binary_sensor:
- platform: status
name: "Status"
@@ -468,12 +546,20 @@ button:
entity_category: config
on_press:
then:
- - lambda: |-
- id(remote_pairing) = true;
- id(remote_pair_deadline) = millis() + 60000;
- id(remote_pairing_status).publish_state("Pairing for 60 seconds");
- ESP_LOGI("espnow_remote", "Pairing enabled for 60 seconds; press a ESP-NOW remote button");
- - script.execute: remote_pairing_timeout
+ - if:
+ condition:
+ lambda: 'return !id(espnow_enabled);'
+ then:
+ - lambda: |-
+ id(remote_pairing_status).publish_state("Disabled");
+ ESP_LOGW("espnow_remote", "ESP-NOW is disabled; turn on the ESP-NOW switch before pairing");
+ else:
+ - lambda: |-
+ id(remote_pairing) = true;
+ id(remote_pair_deadline) = millis() + 60000;
+ id(remote_pairing_status).publish_state("Pairing for 60 seconds");
+ ESP_LOGI("espnow_remote", "Pairing enabled for 60 seconds; press a ESP-NOW remote button");
+ - script.execute: remote_pairing_timeout
- platform: template
name: "Clear ESP-NOW Remote"
@@ -483,20 +569,28 @@ button:
then:
- if:
condition:
- lambda: 'return id(remote_paired);'
+ lambda: 'return !id(espnow_enabled);'
then:
- - espnow.peer.delete:
- id: espnow_component
- address: !lambda |-
- return id(remote_mac);
- - lambda: |-
- id(remote_mac) = std::array{0, 0, 0, 0, 0, 0};
- id(remote_paired) = false;
- id(remote_pairing) = false;
- id(remote_last_seq) = 0xFFFFFFFF;
- id(remote_paired_text).publish_state("None");
- id(remote_pairing_status).publish_state("Idle");
- ESP_LOGI("espnow_remote", "Cleared paired ESP-NOW remote");
+ - lambda: |-
+ id(remote_pairing_status).publish_state("Disabled");
+ ESP_LOGW("espnow_remote", "ESP-NOW is disabled; turn on the ESP-NOW switch before clearing the remote");
+ else:
+ - if:
+ condition:
+ lambda: 'return id(remote_paired);'
+ then:
+ - espnow.peer.delete:
+ id: espnow_component
+ address: !lambda |-
+ return id(remote_mac);
+ - lambda: |-
+ id(remote_mac) = std::array{0, 0, 0, 0, 0, 0};
+ id(remote_paired) = false;
+ id(remote_pairing) = false;
+ id(remote_last_seq) = 0xFFFFFFFF;
+ id(remote_paired_text).publish_state("None");
+ id(remote_pairing_status).publish_state("Idle");
+ ESP_LOGI("espnow_remote", "Cleared paired ESP-NOW remote");
output:
- platform: ledc
@@ -522,13 +616,13 @@ output:
frequency: 9765
pin: GPIO3
min_power: 0
- max_power: 0.9
+ max_power: 0.95
- platform: ledc
id: warm_white_output
frequency: 9765
pin: GPIO4
min_power: 0
- max_power: 0.9
+ max_power: 0.95
light:
- platform: rgbww