Add infrared learning function

This commit is contained in:
Aiden
2026-07-06 19:35:34 +08:00
parent 86ef2ca1ee
commit 8d5e923902
5 changed files with 250 additions and 1 deletions
Vendored
BIN
View File
Binary file not shown.
+131 -1
View File
@@ -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.1"
project_version: "v3.0.2"
# 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")
@@ -70,6 +70,25 @@ esp32:
preferences:
flash_write_interval: 1min
# Global variables for IR learning
globals:
- id: is_learning_mode
type: bool
restore_value: no
initial_value: 'false'
- id: signal_select_index
type: int
restore_value: yes
initial_value: '0'
- id: filter_size
type: int
restore_value: no
initial_value: '10'
# Flash storage component for saving IR signals
Flash_comp:
id: signal_nvs
api:
reboot_timeout: 0s
# Only enable BLE tracking when wifi is up and api is connected
@@ -132,6 +151,11 @@ esp32_improv:
dashboard_import:
package_import_url: github://athom-tech/esp32-configs/athom-rf-ir-remote.yaml
# Include the local Flash storage component
external_components:
- source: github://athom-tech/esp32-configs@main
components: [Flash_comp]
remote_receiver:
- pin:
number: ${RF_RX_PIN}
@@ -145,7 +169,21 @@ remote_receiver:
inverted: true
dump: all
tolerance: 25%
idle: 65500us
clock_resolution: "500000"
id: ir_receiver
on_raw:
- lambda: |-
if (id(is_learning_mode)) {
if (x.size() > id(filter_size)) {
id(signal_nvs).save_to_nvs(id(signal_select_index), x);
ESP_LOGI("IR_LEARNING", "Signal saved to slot %d, size: %d", id(signal_select_index), x.size());
id(is_learning_mode) = false;
id(status_text).publish_state("Signal learned!");
} else {
ESP_LOGW("IR_LEARNING", "Signal too short: %d", x.size());
}
}
remote_transmitter:
- pin:
@@ -251,6 +289,34 @@ button:
internal: false
entity_category: config
# IR learning button
- platform: template
name: "IR Learn"
icon: mdi:remote-tv
on_press:
- lambda: |-
id(is_learning_mode) = true;
ESP_LOGI("IR_LEARNING", "Learning mode activated for slot %d", id(signal_select_index));
id(status_text).publish_state("Ready to learn...");
# IR send button
- platform: template
name: "IR Send"
icon: mdi:send
on_press:
- script.execute: send_raw_signal
# Clear the signal in the current slot
- platform: template
name: "IR Clear Slot"
icon: mdi:delete
on_press:
- lambda: |-
std::vector<int> empty_signal;
id(signal_nvs).save_to_nvs(id(signal_select_index), empty_signal);
ESP_LOGI("IR_LEARNING", "Cleared slot %d", id(signal_select_index));
id(status_text).publish_state("Slot cleared");
light:
- platform: status_led
name: "Status LED"
@@ -278,6 +344,70 @@ text_sensor:
icon: mdi:clock
entity_category: diagnostic
# IR learning status display
- platform: template
name: "IR Learning Status"
id: status_text
icon: mdi:information
# Signal slot selector
select:
- platform: template
name: "IR Signal Slot"
id: 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(signal_select_index) = 0;
else if (x == "Signal 1") id(signal_select_index) = 1;
else if (x == "Signal 2") id(signal_select_index) = 2;
else if (x == "Signal 3") id(signal_select_index) = 3;
else if (x == "Signal 4") id(signal_select_index) = 4;
else if (x == "Signal 5") id(signal_select_index) = 5;
else if (x == "Signal 6") id(signal_select_index) = 6;
else if (x == "Signal 7") id(signal_select_index) = 7;
else if (x == "Signal 8") id(signal_select_index) = 8;
else if (x == "Signal 9") id(signal_select_index) = 9;
ESP_LOGI("IR_LEARNING", "Selected slot: %d", id(signal_select_index));
# Script for sending raw IR signals
script:
- id: send_raw_signal
then:
- lambda: |-
std::vector<int> signal_data = id(signal_nvs).load_from_nvs<int>(id(signal_select_index));
if (signal_data.size() > 0) {
ESP_LOGI("IR_SEND", "Sending signal from slot %d, size: %d", id(signal_select_index), signal_data.size());
id(status_text).publish_state("Sending signal...");
auto transmit = id(ir_transmitter).transmit();
auto data = transmit.get_data();
data->set_carrier_frequency(38000);
for (int i = 0; i < signal_data.size(); i++) {
if (i % 2 == 0) {
data->mark(signal_data[i]);
} else {
data->space(signal_data[i]);
}
}
transmit.perform();
id(status_text).publish_state("Signal sent!");
} else {
ESP_LOGW("IR_SEND", "No signal stored in slot %d", id(signal_select_index));
id(status_text).publish_state("No signal in this slot");
}
time:
- platform: sntp
id: sntp_time
+28
View File
@@ -0,0 +1,28 @@
#include "Flash_comp.h"
namespace esphome {
namespace flash_component {
void Flash_comp::setup() {
nvs_flash_init();
}
bool Flash_comp::clear_signal_by_index(int index) {
char key[12];
sprintf(key, "irsig_%d", index);
nvs_handle_t handle;
if (nvs_open("storage", NVS_READWRITE, &handle) != ESP_OK) {
ESP_LOGE("flash_comp", "Failed to open NVS handle");
return false;
}
nvs_erase_key(handle, key);
nvs_commit(handle);
nvs_close(handle);
return true;
}
} // namespace flash_component
} // namespace esphome
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include "esphome.h"
#include "esphome/core/component.h"
#include <vector>
#include <nvs_flash.h>
namespace esphome {
namespace flash_component {
class Flash_comp : public Component {
public:
void setup() override;
template<typename T>
bool save_to_nvs(int index, const std::vector<T> data){
char key[12];
sprintf(key, "irsig_%d", index);
nvs_handle_t handle;
if (nvs_open("storage", NVS_READWRITE, &handle) != ESP_OK) {
ESP_LOGE("flash_comp", "Failed to open NVS handle");
return false;
}
// delete old data
nvs_erase_key(handle, key);
// store new data
if (data.size() > 0) {
esp_err_t err = nvs_set_blob(handle, key, data.data(), data.size() * sizeof(long int));
if (err != ESP_OK) {
ESP_LOGE("flash_comp", "Failed to write data: %d", err);
return false;
}
}
ESP_LOGI("flash_comp", "Saved %d data to NVS", data.size());
// commit changes
nvs_commit(handle);
nvs_close(handle);
return true;
}
template<typename T>
std::vector<T> load_from_nvs(int index) {
char key[12];
sprintf(key, "irsig_%d", index);
std::vector<T> data;
nvs_handle_t handle;
if (nvs_open("storage", NVS_READONLY, &handle) != ESP_OK) {
ESP_LOGE("flash_comp", "Failed to open NVS handle");
return data;
}
// get data size
size_t required_size = 0;
esp_err_t err = nvs_get_blob(handle, key, nullptr, &required_size);
if (err == ESP_OK && required_size > 0) {
// add data to vector
data.resize(required_size / sizeof(long int));
nvs_get_blob(handle, key, data.data(), &required_size);
} else if (err != ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGE("flash_comp", "Failed to read data: %d", err);
}
ESP_LOGI("flash_comp", "Loaded %d data from NVS", data.size());
nvs_close(handle);
return data;
}
bool clear_signal_by_index(int index);
};
} // namespace flash_component
} // namespace esphome
+14
View File
@@ -0,0 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
flash_comp_ns = cg.esphome_ns.namespace('flash_component')
Flash_comp = flash_comp_ns.class_('Flash_comp', cg.Component)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(Flash_comp),
}).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)