From 9d02ab3734d003d36fe63964f57e9f8a339daa37 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 9 Oct 2026 09:22:32 -1000 Subject: [PATCH] [remote_base] Keep constant IR codes in flash (#20083) --- esphome/components/remote_base/__init__.py | 32 ++++++++------ .../components/remote_base/aeha_protocol.h | 3 +- .../components/remote_base/haier_protocol.h | 2 +- .../components/remote_base/midea_protocol.h | 10 ++++- .../components/remote_base/mirage_protocol.h | 2 +- .../remote_transmitter/config/ir_codes.yaml | 43 +++++++++++++++++++ .../remote_transmitter/test_ir_codes.py | 24 +++++++++++ 7 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 tests/component_tests/remote_transmitter/config/ir_codes.yaml create mode 100644 tests/component_tests/remote_transmitter/test_ir_codes.py diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 872106e405..f8e0b4d33c 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -2061,11 +2061,20 @@ def midea_dumper(var, config): pass +async def _code_bytes( + value: Any, args: TemplateArgsType, set_template: MockObj, set_static: MockObj +) -> None: + """Constant codes share one PROGMEM table pool across the remote protocols.""" + await automation.templatable_bytes( + value, args, set_template, set_static, "remote_base_code" + ) + + @register_action("midea", MideaAction, MIDEA_SCHEMA) async def midea_action(var, config, args): - vec_ = cg.std_vector.template(cg.uint8) - template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) - cg.add(var.set_code(template_)) + await _code_bytes( + config[CONF_CODE], args, var.set_code_template, var.set_code_static + ) # AEHA @@ -2117,10 +2126,9 @@ def aeha_dumper(var, config): async def aeha_action(var, config, args): template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) cg.add(var.set_address(template_)) - template_ = await cg.templatable( - config[CONF_DATA], args, cg.std_vector.template(cg.uint8) + await _code_bytes( + config[CONF_DATA], args, var.set_data_template, var.set_data_static ) - cg.add(var.set_data(template_)) templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32) cg.add(var.set_carrier_frequency(templ)) @@ -2216,9 +2224,9 @@ def haier_dumper(var, config): @register_action("haier", HaierAction, HAIER_SCHEMA) async def haier_action(var, config, args): - vec_ = cg.std_vector.template(cg.uint8) - template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) - cg.add(var.set_code(template_)) + await _code_bytes( + config[CONF_CODE], args, var.set_code_template, var.set_code_static + ) # ABBWelcome @@ -2359,9 +2367,9 @@ def mirage_dumper(var, config): @register_action("mirage", MirageAction, MIRAGE_SCHEMA) async def mirage_action(var, config, args): - vec_ = cg.std_vector.template(cg.uint8) - template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) - cg.add(var.set_code(template_)) + await _code_bytes( + config[CONF_CODE], args, var.set_code_template, var.set_code_static + ) # Toto diff --git a/esphome/components/remote_base/aeha_protocol.h b/esphome/components/remote_base/aeha_protocol.h index 98a5501155..57706a7e4f 100644 --- a/esphome/components/remote_base/aeha_protocol.h +++ b/esphome/components/remote_base/aeha_protocol.h @@ -28,10 +28,9 @@ DECLARE_REMOTE_PROTOCOL(AEHA) template class AEHAAction : public RemoteTransmitterActionBase { public: TEMPLATABLE_VALUE(uint16_t, address) - TEMPLATABLE_VALUE(std::vector, data) + TEMPLATABLE_BYTES(data) TEMPLATABLE_VALUE(uint32_t, carrier_frequency); - void set_data(const std::vector &data) { data_ = data; } void encode(RemoteTransmitData *dst, Ts... x) override { AEHAData data{}; data.address = this->address_.value(x...); diff --git a/esphome/components/remote_base/haier_protocol.h b/esphome/components/remote_base/haier_protocol.h index e1fd60411f..90af88d2c7 100644 --- a/esphome/components/remote_base/haier_protocol.h +++ b/esphome/components/remote_base/haier_protocol.h @@ -25,7 +25,7 @@ DECLARE_REMOTE_PROTOCOL(Haier) template class HaierAction : public RemoteTransmitterActionBase { public: - TEMPLATABLE_VALUE(std::vector, code) + TEMPLATABLE_BYTES(code) void encode(RemoteTransmitData *dst, Ts... x) override { HaierData data{}; diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 85bbef1cb1..19521e8f6d 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -4,6 +4,7 @@ #include #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "remote_base.h" @@ -21,6 +22,9 @@ class MideaData { MideaData(const std::vector &data) { std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin()); } + MideaData(const uint8_t *data, size_t len) { + progmem_memcpy(this->data_.data(), data, std::min(len, this->data_.size())); + } uint8_t *data() { return this->data_.data(); } const uint8_t *data() const { return this->data_.data(); } @@ -75,10 +79,12 @@ class MideaProtocol : public RemoteProtocol { DECLARE_REMOTE_PROTOCOL(Midea) template class MideaAction : public RemoteTransmitterActionBase { - TEMPLATABLE_VALUE(std::vector, code) + public: + TEMPLATABLE_BYTES(code) void encode(RemoteTransmitData *dst, Ts... x) override { - MideaData data(this->code_.value(x...)); + MideaData data = this->code_.is_static() ? MideaData(this->code_.data(), this->code_.size()) + : MideaData(this->code_.value(x...)); data.finalize(); MideaProtocol().encode(dst, data); } diff --git a/esphome/components/remote_base/mirage_protocol.h b/esphome/components/remote_base/mirage_protocol.h index a37fb93f4f..958127ba66 100644 --- a/esphome/components/remote_base/mirage_protocol.h +++ b/esphome/components/remote_base/mirage_protocol.h @@ -25,7 +25,7 @@ DECLARE_REMOTE_PROTOCOL(Mirage) template class MirageAction : public RemoteTransmitterActionBase { public: - TEMPLATABLE_VALUE(std::vector, code) + TEMPLATABLE_BYTES(code) void encode(RemoteTransmitData *dst, Ts... x) override { MirageData data{}; diff --git a/tests/component_tests/remote_transmitter/config/ir_codes.yaml b/tests/component_tests/remote_transmitter/config/ir_codes.yaml new file mode 100644 index 0000000000..69cf1fb039 --- /dev/null +++ b/tests/component_tests/remote_transmitter/config/ir_codes.yaml @@ -0,0 +1,43 @@ +esphome: + name: test + +esp32: + board: esp32dev + +remote_transmitter: + pin: GPIO4 + carrier_duty_percent: 50% + +button: + - platform: template + name: Midea A + on_press: + - remote_transmitter.transmit_midea: + code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] + - platform: template + name: Midea B + on_press: + - remote_transmitter.transmit_midea: + code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] + - platform: template + name: AEHA + on_press: + - remote_transmitter.transmit_aeha: + address: 0x8008 + data: [0x00, 0x02, 0xFD, 0xFF, 0x00, 0x33] + - platform: template + name: Haier lambda + on_press: + - remote_transmitter.transmit_haier: + code: !lambda |- + return {0xA6, 0xDA, 0x00, 0x00}; + - platform: template + name: Haier static + on_press: + - remote_transmitter.transmit_haier: + code: [0xA6, 0xDA, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x05] + - platform: template + name: Mirage static + on_press: + - remote_transmitter.transmit_mirage: + code: [0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x42] diff --git a/tests/component_tests/remote_transmitter/test_ir_codes.py b/tests/component_tests/remote_transmitter/test_ir_codes.py new file mode 100644 index 0000000000..9be34941b6 --- /dev/null +++ b/tests/component_tests/remote_transmitter/test_ir_codes.py @@ -0,0 +1,24 @@ +"""Constant IR byte codes live in shared PROGMEM tables; lambdas stay lambdas.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_ir_codes_use_shared_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("ir_codes.yaml")) + + tables = re.findall( + r"static constexpr uint8_t (remote_base_code\w*)\[\] PROGMEM", main_cpp + ) + assert len(tables) == 4 # Midea (shared by two actions), AEHA, Haier, Mirage + midea = tables[0] + assert main_cpp.count(f"->set_code_static({midea}, 5);") == 2 + assert re.search(r"->set_data_static\(remote_base_code\w*, 6\);", main_cpp) + assert re.search(r"->set_code_static\(remote_base_code\w*, 13\);", main_cpp) + assert re.search(r"->set_code_static\(remote_base_code\w*, 14\);", main_cpp) + assert "->set_code_template([](" in main_cpp + assert "std::vector{" not in main_cpp