From a512f91473f478e765889d5e42a0add670375d8d Mon Sep 17 00:00:00 2001
From: Aiden
Date: Sat, 18 Jul 2026 12:04:25 +0800
Subject: [PATCH] Add RF433 radio frequency learning function
---
athom-rf-ir-remote.yaml | 148 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 146 insertions(+), 2 deletions(-)
diff --git a/athom-rf-ir-remote.yaml b/athom-rf-ir-remote.yaml
index 222afa8..1f389a5 100644
--- a/athom-rf-ir-remote.yaml
+++ b/athom-rf-ir-remote.yaml
@@ -10,7 +10,7 @@ substitutions:
# Project Name
project_name: "China Athom Technology.Athom RF IR Remote"
# Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
- project_version: "v3.0.4"
+ project_version: "v3.0.5"
# 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)
dns_domain: ".local"
# Set timezone of the smart plug. Useful if the plug is in a location different to the HA server. Can be entered in unix Country/Area format (i.e. "Australia/Sydney")
@@ -84,6 +84,26 @@ globals:
type: int
restore_value: no
initial_value: '10'
+ # RF433 learning mode flag (independent from IR learning)
+ - id: is_rf_learning_mode
+ type: bool
+ restore_value: no
+ initial_value: 'false'
+ # RF433 slot index, offset to 10-19 so it never collides with IR slots 0-9
+ - id: rf_signal_select_index
+ type: int
+ restore_value: yes
+ initial_value: '10'
+ # Temp holders for the rc_switch code/protocol to transmit; referenced by
+ # transmit_rc_switch_raw's !lambda during playback
+ - id: rf_tx_code
+ type: uint64_t
+ restore_value: no
+ initial_value: '0'
+ - id: rf_tx_protocol
+ type: int
+ restore_value: no
+ initial_value: '0'
# Flash storage component for saving IR signals
Flash_comp:
@@ -178,8 +198,26 @@ remote_receiver:
number: ${RF_RX_PIN}
inverted: true
dump: rc_switch
- tolerance: 25%
+ # RF433 fixed-code decoding: filter glitches, split frames on 4ms idle,
+ # and widen tolerance for cheap remotes. rc_switch decoding rejects the
+ # receiver's ambient noise, so learning no longer false-triggers.
+ filter: 4us
+ idle: 4ms
+ tolerance: 50%
id: rf_receiver
+ on_rc_switch:
+ - lambda: |-
+ if (id(is_rf_learning_mode)) {
+ std::vector d;
+ d.push_back((int) x.protocol);
+ d.push_back((int) (x.code >> 32));
+ d.push_back((int) (x.code & 0xFFFFFFFF));
+ id(signal_nvs).save_to_nvs(id(rf_signal_select_index), d);
+ ESP_LOGI("RF_LEARNING", "Learned protocol=%u code=%llu slot=%d",
+ x.protocol, (unsigned long long) x.code, id(rf_signal_select_index));
+ id(is_rf_learning_mode) = false;
+ id(rf_status_text).publish_state("Signal learned!");
+ }
- pin:
number: ${IR_RX_PIN}
@@ -335,6 +373,34 @@ button:
ESP_LOGI("IR_LEARNING", "Cleared slot %d", id(signal_select_index));
id(status_text).publish_state("Slot cleared");
+ # RF433 learning button
+ - platform: template
+ name: "RF Learn"
+ icon: mdi:remote
+ on_press:
+ - lambda: |-
+ id(is_rf_learning_mode) = true;
+ ESP_LOGI("RF_LEARNING", "Learning mode activated for slot %d", id(rf_signal_select_index));
+ id(rf_status_text).publish_state("Ready to learn...");
+
+ # RF433 send button
+ - platform: template
+ name: "RF Send"
+ icon: mdi:send
+ on_press:
+ - script.execute: send_raw_rf_signal
+
+ # Clear the RF signal in the current slot
+ - platform: template
+ name: "RF Clear Slot"
+ icon: mdi:delete
+ on_press:
+ - lambda: |-
+ std::vector empty_signal;
+ id(signal_nvs).save_to_nvs(id(rf_signal_select_index), empty_signal);
+ ESP_LOGI("RF_LEARNING", "Cleared slot %d", id(rf_signal_select_index));
+ id(rf_status_text).publish_state("Slot cleared");
+
light:
- platform: status_led
name: "Status LED"
@@ -368,6 +434,12 @@ text_sensor:
id: status_text
icon: mdi:information
+ # RF433 learning status display
+ - platform: template
+ name: "RF Learning Status"
+ id: rf_status_text
+ icon: mdi:information
+
# Signal slot selector
select:
- platform: template
@@ -400,6 +472,37 @@ select:
else if (x == "Signal 9") id(signal_select_index) = 9;
ESP_LOGI("IR_LEARNING", "Selected slot: %d", id(signal_select_index));
+ # RF433 signal slot selector (mapped to NVS index 10-19 to avoid IR collision)
+ - platform: template
+ name: "RF Signal Slot"
+ id: rf_signal_slot_select
+ optimistic: true
+ options:
+ - "Signal 0"
+ - "Signal 1"
+ - "Signal 2"
+ - "Signal 3"
+ - "Signal 4"
+ - "Signal 5"
+ - "Signal 6"
+ - "Signal 7"
+ - "Signal 8"
+ - "Signal 9"
+ initial_option: "Signal 0"
+ on_value:
+ - lambda: |-
+ if (x == "Signal 0") id(rf_signal_select_index) = 10;
+ else if (x == "Signal 1") id(rf_signal_select_index) = 11;
+ else if (x == "Signal 2") id(rf_signal_select_index) = 12;
+ else if (x == "Signal 3") id(rf_signal_select_index) = 13;
+ else if (x == "Signal 4") id(rf_signal_select_index) = 14;
+ else if (x == "Signal 5") id(rf_signal_select_index) = 15;
+ else if (x == "Signal 6") id(rf_signal_select_index) = 16;
+ else if (x == "Signal 7") id(rf_signal_select_index) = 17;
+ else if (x == "Signal 8") id(rf_signal_select_index) = 18;
+ else if (x == "Signal 9") id(rf_signal_select_index) = 19;
+ ESP_LOGI("RF_LEARNING", "Selected slot: %d", id(rf_signal_select_index));
+
# Script for sending raw IR signals
script:
- id: send_raw_signal
@@ -426,6 +529,47 @@ script:
id(status_text).publish_state("No signal in this slot");
}
+ # Script for sending learned RF433 rc_switch signals
+ - id: send_raw_rf_signal
+ then:
+ # Load [protocol, code_high, code_low] from NVS and rebuild the 64-bit code
+ # into the temp globals that transmit_rc_switch_raw reads via !lambda.
+ - lambda: |-
+ std::vector d = id(signal_nvs).load_from_nvs(id(rf_signal_select_index));
+ if (d.size() >= 3) {
+ id(rf_tx_protocol) = d[0];
+ id(rf_tx_code) = ((uint64_t) (uint32_t) d[1] << 32) | (uint32_t) d[2];
+ ESP_LOGI("RF_SEND", "Sending protocol=%d code=%llu slot=%d",
+ id(rf_tx_protocol), (unsigned long long) id(rf_tx_code), id(rf_signal_select_index));
+ id(rf_status_text).publish_state("Sending signal...");
+ } else {
+ id(rf_tx_protocol) = 0;
+ ESP_LOGW("RF_SEND", "No signal stored in slot %d", id(rf_signal_select_index));
+ id(rf_status_text).publish_state("No signal in this slot");
+ }
+ - if:
+ condition:
+ lambda: 'return id(rf_tx_protocol) > 0;'
+ then:
+ - remote_transmitter.transmit_rc_switch_raw:
+ # protocol must be an RCSwitchBase: index the built-in table (1..8)
+ protocol: !lambda 'return esphome::remote_base::RC_SWITCH_PROTOCOLS[id(rf_tx_protocol)];'
+ # code must be an MSB-first binary string; its length is the bit count.
+ # on_rc_switch does not expose the received bit length, and virtually
+ # all 433MHz fixed-code remotes (EV1527/PT2262/…) are 24-bit.
+ code: !lambda |-
+ uint64_t code = id(rf_tx_code);
+ const int nbits = 24;
+ std::string s;
+ for (int i = nbits - 1; i >= 0; i--)
+ s += (code & ((uint64_t) 1 << i)) ? '1' : '0';
+ return s;
+ transmitter_id: rf_transmitter
+ repeat:
+ times: 5
+ wait_time: 0us
+ - lambda: 'id(rf_status_text).publish_state("Signal sent!");'
+
time:
- platform: sntp
id: sntp_time