diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 1911218b1f..eca57bca8a 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -44,7 +44,7 @@ from esphome.const import ( CONF_WAND_ID, CONF_ZERO, ) -from esphome.core import ID, coroutine +from esphome.core import coroutine from esphome.cpp_generator import MockObj from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome.types import ConfigType, TemplateArgsType @@ -2393,17 +2393,14 @@ async def abbwelcome_action(var, config, args): await cg.templatable(config[CONF_MESSAGE_ID], args, cg.uint8) ) ) - if CONF_DATA in config: - data_ = config[CONF_DATA] - if cg.is_template(data_): - template_ = await cg.templatable( - data_, args, cg.std_vector.template(cg.uint8) - ) - cg.add(var.set_data_template(template_)) - else: - arr_id = ID(f"{var.base}_data", is_declaration=True, type=cg.uint8) - arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data_)) - cg.add(var.set_data_static(arr, len(data_))) + # TemplatableBytes starts in lambda mode with no function, so a transmit without data sets an empty payload + await automation.templatable_bytes( + config.get(CONF_DATA, []), + args, + var.set_data_template, + var.set_data_static, + "abbwelcome_data", + ) # Mirage diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index a309c124ee..901950d363 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -112,13 +112,14 @@ class ABBWelcomeData { } void set_message_id(uint8_t message_id) { this->data_[4 + 2 * this->get_address_length()] = message_id; } uint8_t get_message_id() const { return this->data_[4 + 2 * this->get_address_length()]; } - void set_data(std::vector data) { - uint8_t size = std::min(MAX_DATA_LENGTH, static_cast(data.size())); + void set_data(const uint8_t *data, size_t len) { + uint8_t size = static_cast(std::min(MAX_DATA_LENGTH, len)); this->data_[2] &= (0xff ^ DATA_LENGTH_MASK); this->data_[2] |= (size & DATA_LENGTH_MASK); if (size) - std::copy_n(data.begin(), size, this->data_.begin() + 5 + 2 * this->get_address_length()); + std::copy_n(data, size, this->data_.begin() + 5 + 2 * this->get_address_length()); } + void set_data(const std::vector &data) { this->set_data(data.data(), data.size()); } std::vector get_data() const { std::vector data(this->data_.begin() + 5 + 2 * this->get_address_length(), this->data_.begin() + 5 + 2 * this->get_address_length() + this->get_data_size()); @@ -233,14 +234,8 @@ template class ABBWelcomeAction : public RemoteTransmitterAction TEMPLATABLE_VALUE(uint8_t, message_type) TEMPLATABLE_VALUE(uint8_t, message_id) TEMPLATABLE_VALUE(bool, auto_message_id) - void set_data_template(std::vector (*func)(Ts...)) { - this->data_.func = func; - this->len_ = -1; // Sentinel value indicates template mode - } - void set_data_static(const uint8_t *data, size_t len) { - this->data_.data = data; - this->len_ = len; // Length >= 0 indicates static mode - } + TEMPLATABLE_BYTES(data) + void encode(RemoteTransmitData *dst, Ts... x) override { ABBWelcomeData data; data.set_three_byte_address(this->three_byte_address_.value(x...)); @@ -250,25 +245,11 @@ template class ABBWelcomeAction : public RemoteTransmitterAction data.set_message_type(this->message_type_.value(x...)); data.set_message_id(this->message_id_.value(x...)); data.auto_message_id = this->auto_message_id_.value(x...); - std::vector data_vec; - if (this->len_ > 0) { - // Static mode: copy from flash to vector - data_vec.assign(this->data_.data, this->data_.data + this->len_); - } else if (this->len_ < 0) { - // Template mode: call function - data_vec = this->data_.func(x...); - } - data.set_data(data_vec); + this->data_.template visit( + [&data](const uint8_t *bytes, size_t len) { data.set_data(bytes, len); }, x...); data.finalize(); ABBWelcomeProtocol().encode(dst, data); } - - protected: - ssize_t len_{0}; // <0 = template mode, >=0 = static mode with length - union Data { - std::vector (*func)(Ts...); // Function pointer (stateless lambdas) - const uint8_t *data; // Pointer to static data in flash - } data_; }; } // namespace esphome::remote_base diff --git a/tests/component_tests/remote_transmitter/config/abbwelcome_payload_tables.yaml b/tests/component_tests/remote_transmitter/config/abbwelcome_payload_tables.yaml new file mode 100644 index 0000000000..66e0fe4e7f --- /dev/null +++ b/tests/component_tests/remote_transmitter/config/abbwelcome_payload_tables.yaml @@ -0,0 +1,36 @@ +--- +esphome: + name: test + on_boot: + then: + - remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x01 + data: [0x10, 0x20, 0x30] + - remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x02 + data: [0x10, 0x20, 0x30] + - remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x03 + data: [] + - remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x04 + - remote_transmitter.transmit_abbwelcome: + source_address: 0x1234 + destination_address: 0x5678 + message_type: 0x05 + data: !lambda return {0x09}; + +esp32: + board: esp32dev + +remote_transmitter: + pin: 4 + carrier_duty_percent: 100% diff --git a/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py b/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py new file mode 100644 index 0000000000..a653b5137e --- /dev/null +++ b/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py @@ -0,0 +1,21 @@ +"""Tests for abbwelcome constant data in shared PROGMEM tables.""" + +from collections.abc import Callable +from pathlib import Path +import re + + +def test_constant_data_shares_progmem_tables( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + """Equal data shares one table, empty or missing data is an empty table, lambdas stay templates.""" + main_cpp = generate_main(component_config_path("abbwelcome_payload_tables.yaml")) + + tables = re.findall( + r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp + ) + assert [v for _, v in tables] == ["{0x10, 0x20, 0x30}"] + assert main_cpp.count(f"set_data_static({tables[0][0]}, 3);") == 2 + assert main_cpp.count("set_data_static(nullptr, 0);") == 2 + assert main_cpp.count("set_data_template(") == 1