diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 93e26165d6..45873f2385 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -322,18 +322,33 @@ def _protocol_field( return automation.ApplyField(key, f"data.{member} = {{}}", type_) +_BYTES = cg.std_vector.template(cg.uint8) + + +def _code_table(config: ConfigType, value: list[int]) -> str: + return f"{automation.progmem_bytes('remote_base_code', value)}, {len(value)}" + + +def _bytes_field(conf_key: str, target: str) -> automation.ApplyField: + """A constant's flash table and length, or a lambda's vector; the callee takes both.""" + return automation.ApplyField(conf_key, target, _BYTES, const_fn=_code_table) + + def register_protocol_action( name: str, protocol: str, schema: cv.Schema, *fields: ProtocolField, validate: Callable[[ConfigType], ConfigType] | None = None, + call: str = "protocol_call", ) -> None: """Register ``transmit_`` with no C++ action class. One generated function fills ``Data`` from ``(conf_key, member, type_)`` fields and sends it; the action is a core ``ApplyAction``. ``ApplyField`` and ``ApplyCall`` pass through and target the call object, whose protocol data is ``data``. + ``call="encode_call"`` targets a plain transmit call whose ``encode()`` runs one of the + protocol's encoders, for payloads it encodes straight from a pointer and length. """ action, schema, validator = _transmit_action(name, schema, validate) builder = automation.apply_action_builder( @@ -344,7 +359,7 @@ def register_protocol_action( automation.ApplyField( (CONF_REPEAT, CONF_WAIT_TIME), "set_send_wait", cg.uint32 ), - call=f"protocol_call<{ns.class_(f'{protocol}Protocol')}>", + call=f"{call}<{ns.class_(f'{protocol}Protocol')}>", id_key=CONF_TRANSMITTER_ID, ) @@ -2375,10 +2390,7 @@ register_protocol_action( # Midea -MideaData, MideaBinarySensor, MideaTrigger, MideaAction, MideaDumper = declare_protocol( - "Midea" -) -MideaAction = ns.class_("MideaAction", RemoteTransmitterActionBase) +MideaData, MideaBinarySensor, MideaTrigger, _, MideaDumper = declare_protocol("Midea") MIDEA_SCHEMA = cv.Schema( { cv.Required(CONF_CODE): cv.All([cv.hex_uint8_t], cv.Length(min=5, max=5)), @@ -2401,26 +2413,17 @@ 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): - await _code_bytes( - config[CONF_CODE], args, var.set_code_template, var.set_code_static - ) +register_protocol_action( + "midea", + "Midea", + MIDEA_SCHEMA, + _bytes_field(CONF_CODE, f"data = {MideaData}({{}})"), + automation.ApplyCall("data.finalize()"), +) # AEHA -AEHAData, AEHABinarySensor, AEHATrigger, AEHAAction, AEHADumper = declare_protocol( - "AEHA" -) +AEHAData, AEHABinarySensor, AEHATrigger, _, AEHADumper = declare_protocol("AEHA") AEHA_SCHEMA = cv.Schema( { cv.Required(CONF_ADDRESS): cv.hex_uint16_t, @@ -2452,9 +2455,9 @@ def aeha_dumper(var, config): pass -@register_action( +register_protocol_action( "aeha", - AEHAAction, + "AEHA", AEHA_SCHEMA.extend( { cv.Optional(CONF_CARRIER_FREQUENCY, default="38000Hz"): cv.All( @@ -2462,15 +2465,18 @@ def aeha_dumper(var, config): ), } ), + automation.ApplyField( + CONF_CARRIER_FREQUENCY, "get_data()->set_carrier_frequency({})", cg.uint32 + ), + automation.ApplyCall( + "encode({}, {})", + ( + (CONF_ADDRESS, cg.uint16), + (CONF_DATA, _BYTES, _code_table), + ), + ), + call="encode_call", ) -async def aeha_action(var, config, args): - template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) - cg.add(var.set_address(template_)) - await _code_bytes( - config[CONF_DATA], args, var.set_data_template, var.set_data_static - ) - templ = await cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32) - cg.add(var.set_carrier_frequency(templ)) # Hob2Hood @@ -2529,10 +2535,7 @@ register_protocol_action( # Haier -HaierData, HaierBinarySensor, HaierTrigger, HaierAction, HaierDumper = declare_protocol( - "Haier" -) -HaierAction = ns.class_("HaierAction", RemoteTransmitterActionBase) +HaierData, HaierBinarySensor, HaierTrigger, _, HaierDumper = declare_protocol("Haier") HAIER_SCHEMA = cv.Schema( { cv.Required(CONF_CODE): cv.All( @@ -2562,11 +2565,13 @@ def haier_dumper(var, config): pass -@register_action("haier", HaierAction, HAIER_SCHEMA) -async def haier_action(var, config, args): - await _code_bytes( - config[CONF_CODE], args, var.set_code_template, var.set_code_static - ) +register_protocol_action( + "haier", + "Haier", + HAIER_SCHEMA, + _bytes_field(CONF_CODE, "encode({})"), + call="encode_call", +) # ABBWelcome @@ -2574,7 +2579,7 @@ async def haier_action(var, config, args): ABBWelcomeData, ABBWelcomeBinarySensor, ABBWelcomeTrigger, - ABBWelcomeAction, + _, ABBWelcomeDumper, ) = declare_protocol("ABBWelcome") @@ -2626,49 +2631,28 @@ def abbwelcome_dumper(var, config): pass -@register_action("abbwelcome", ABBWelcomeAction, ABB_WELCOME_SCHEMA) -async def abbwelcome_action(var, config, args): - cg.add( - var.set_three_byte_address( - await cg.templatable(config[CONF_THREE_BYTE_ADDRESS], args, cg.bool_) - ) - ) - cg.add( - var.set_source_address( - await cg.templatable(config[CONF_SOURCE_ADDRESS], args, cg.uint32) - ) - ) - cg.add( - var.set_destination_address( - await cg.templatable(config[CONF_DESTINATION_ADDRESS], args, cg.uint32) - ) - ) - cg.add( - var.set_retransmission( - await cg.templatable(config[CONF_RETRANSMISSION], args, cg.bool_) - ) - ) - cg.add( - var.set_message_type( - await cg.templatable(config[CONF_MESSAGE_TYPE], args, cg.uint8) - ) - ) - template_ = await cg.templatable(CONF_MESSAGE_ID not in config, args, cg.bool_) - cg.add(var.set_auto_message_id(template_)) - if CONF_MESSAGE_ID in config: - cg.add( - var.set_message_id( - await cg.templatable(config[CONF_MESSAGE_ID], args, cg.uint8) - ) - ) - # 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", - ) +register_protocol_action( + "abbwelcome", + "ABBWelcome", + ABB_WELCOME_SCHEMA, + # First: the address length decides where the other fields go + automation.ApplyField( + CONF_THREE_BYTE_ADDRESS, "data.set_three_byte_address({})", cg.bool_ + ), + automation.ApplyField( + CONF_SOURCE_ADDRESS, "data.set_source_address({})", cg.uint32 + ), + automation.ApplyField( + CONF_DESTINATION_ADDRESS, "data.set_destination_address({})", cg.uint32 + ), + automation.ApplyField(CONF_RETRANSMISSION, "data.set_retransmission({})", cg.bool_), + automation.ApplyField(CONF_MESSAGE_TYPE, "data.set_message_type({})", cg.uint8), + # Without message_id, finalize() picks one + automation.ApplyCall("data.auto_message_id = true"), + automation.ApplyField(CONF_MESSAGE_ID, "data.set_fixed_message_id({})", cg.uint8), + _bytes_field(CONF_DATA, "data.set_data({})"), + automation.ApplyCall("data.finalize()"), +) # Mirage @@ -2676,7 +2660,7 @@ async def abbwelcome_action(var, config, args): MirageData, MirageBinarySensor, MirageTrigger, - MirageAction, + _, MirageDumper, ) = declare_protocol("Mirage") @@ -2702,11 +2686,13 @@ def mirage_dumper(var, config): pass -@register_action("mirage", MirageAction, MIRAGE_SCHEMA) -async def mirage_action(var, config, args): - await _code_bytes( - config[CONF_CODE], args, var.set_code_template, var.set_code_static - ) +register_protocol_action( + "mirage", + "Mirage", + MIRAGE_SCHEMA, + _bytes_field(CONF_CODE, "encode({})"), + call="encode_call", +) # Toto diff --git a/esphome/components/remote_base/abbwelcome_protocol.h b/esphome/components/remote_base/abbwelcome_protocol.h index 901950d363..ecd27a95a5 100644 --- a/esphome/components/remote_base/abbwelcome_protocol.h +++ b/esphome/components/remote_base/abbwelcome_protocol.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "remote_base.h" #include @@ -111,13 +112,19 @@ class ABBWelcomeData { return (this->data_[7] << 16) + (this->data_[8] << 8) + this->data_[9]; } void set_message_id(uint8_t message_id) { this->data_[4 + 2 * this->get_address_length()] = message_id; } + /// A configured id, which finalize() keeps instead of picking one + void set_fixed_message_id(uint8_t message_id) { + this->set_message_id(message_id); + this->auto_message_id = false; + } uint8_t get_message_id() const { return this->data_[4 + 2 * this->get_address_length()]; } + /// data may be a PROGMEM table 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, size, this->data_.begin() + 5 + 2 * this->get_address_length()); + progmem_memcpy(this->data_.data() + 5 + 2 * this->get_address_length(), data, size); } void set_data(const std::vector &data) { this->set_data(data.data(), data.size()); } std::vector get_data() const { @@ -226,30 +233,4 @@ class ABBWelcomeBinarySensor : public RemoteReceiverBinarySensorBase { using ABBWelcomeTrigger = RemoteReceiverTrigger; using ABBWelcomeDumper = RemoteReceiverDumper; -template class ABBWelcomeAction : public RemoteTransmitterActionBase { - TEMPLATABLE_VALUE(uint32_t, source_address) - TEMPLATABLE_VALUE(uint32_t, destination_address) - TEMPLATABLE_VALUE(bool, retransmission) - TEMPLATABLE_VALUE(bool, three_byte_address) - TEMPLATABLE_VALUE(uint8_t, message_type) - TEMPLATABLE_VALUE(uint8_t, message_id) - TEMPLATABLE_VALUE(bool, auto_message_id) - TEMPLATABLE_BYTES(data) - - void encode(RemoteTransmitData *dst, Ts... x) override { - ABBWelcomeData data; - data.set_three_byte_address(this->three_byte_address_.value(x...)); - data.set_source_address(this->source_address_.value(x...)); - data.set_destination_address(this->destination_address_.value(x...)); - data.set_retransmission(this->retransmission_.value(x...)); - 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...); - this->data_.template visit( - [&data](const uint8_t *bytes, size_t len) { data.set_data(bytes, len); }, x...); - data.finalize(); - ABBWelcomeProtocol().encode(dst, data); - } -}; - } // namespace esphome::remote_base diff --git a/esphome/components/remote_base/aeha_protocol.cpp b/esphome/components/remote_base/aeha_protocol.cpp index 3263f6b220..70454dbc49 100644 --- a/esphome/components/remote_base/aeha_protocol.cpp +++ b/esphome/components/remote_base/aeha_protocol.cpp @@ -1,4 +1,5 @@ #include "aeha_protocol.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include @@ -28,7 +29,7 @@ void AEHAProtocol::encode(RemoteTransmitData *dst, uint16_t address, const uint8 } for (size_t i = 0; i < len; i++) { - const uint8_t bit = data[i]; + const uint8_t bit = progmem_read_byte(data + i); for (uint8_t mask = 1 << 7; mask != 0; mask >>= 1) { if (bit & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); diff --git a/esphome/components/remote_base/aeha_protocol.h b/esphome/components/remote_base/aeha_protocol.h index f6064dd5df..ab762612e1 100644 --- a/esphome/components/remote_base/aeha_protocol.h +++ b/esphome/components/remote_base/aeha_protocol.h @@ -18,7 +18,11 @@ class AEHAProtocol : public RemoteProtocol { void encode(RemoteTransmitData *dst, const AEHAData &data) { this->encode(dst, data.address, data.data.data(), data.data.size()); } + /// data may be a PROGMEM table void encode(RemoteTransmitData *dst, uint16_t address, const uint8_t *data, size_t len); + void encode(RemoteTransmitData *dst, uint16_t address, const std::vector &data) { + this->encode(dst, address, data.data(), data.size()); + } optional decode(RemoteReceiveData src); void dump(const AEHAData &data); @@ -28,18 +32,4 @@ class AEHAProtocol : public RemoteProtocol { DECLARE_REMOTE_PROTOCOL(AEHA) -template class AEHAAction : public RemoteTransmitterActionBase { - public: - TEMPLATABLE_VALUE(uint16_t, address) - TEMPLATABLE_BYTES(data) - TEMPLATABLE_VALUE(uint32_t, carrier_frequency); - - void encode(RemoteTransmitData *dst, Ts... x) override { - const uint16_t address = this->address_.value(x...); - dst->set_carrier_frequency(this->carrier_frequency_.value(x...)); - this->data_.visit( - [dst, address](const uint8_t *data, size_t len) { AEHAProtocol().encode(dst, address, data, len); }, x...); - } -}; - } // namespace esphome::remote_base diff --git a/esphome/components/remote_base/haier_protocol.cpp b/esphome/components/remote_base/haier_protocol.cpp index e7fa563fb5..07e3793520 100644 --- a/esphome/components/remote_base/haier_protocol.cpp +++ b/esphome/components/remote_base/haier_protocol.cpp @@ -1,4 +1,5 @@ #include "haier_protocol.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" @@ -39,7 +40,7 @@ void HaierProtocol::encode(RemoteTransmitData *dst, const uint8_t *data, size_t dst->mark(BIT_MARK_US); uint8_t checksum = 0; for (size_t i = 0; i < len; i++) { - const uint8_t item = data[i]; + const uint8_t item = progmem_read_byte(data + i); this->encode_byte_(dst, item); checksum += item; } diff --git a/esphome/components/remote_base/haier_protocol.h b/esphome/components/remote_base/haier_protocol.h index 770784b0fc..0594049471 100644 --- a/esphome/components/remote_base/haier_protocol.h +++ b/esphome/components/remote_base/haier_protocol.h @@ -14,7 +14,11 @@ struct HaierData { class HaierProtocol : public RemoteProtocol { public: void encode(RemoteTransmitData *dst, const HaierData &data) { this->encode(dst, data.data.data(), data.data.size()); } + /// data may be a PROGMEM table void encode(RemoteTransmitData *dst, const uint8_t *data, size_t len); + void encode(RemoteTransmitData *dst, const std::vector &data) { + this->encode(dst, data.data(), data.size()); + } optional decode(RemoteReceiveData src); void dump(const HaierData &data); @@ -24,13 +28,4 @@ class HaierProtocol : public RemoteProtocol { DECLARE_REMOTE_PROTOCOL(Haier) -template class HaierAction : public RemoteTransmitterActionBase { - public: - TEMPLATABLE_BYTES(code) - - void encode(RemoteTransmitData *dst, Ts... x) override { - this->code_.visit([dst](const uint8_t *data, size_t len) { HaierProtocol().encode(dst, data, len); }, x...); - } -}; - } // namespace esphome::remote_base diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 19521e8f6d..572179605a 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -78,16 +78,4 @@ class MideaProtocol : public RemoteProtocol { DECLARE_REMOTE_PROTOCOL(Midea) -template class MideaAction : public RemoteTransmitterActionBase { - public: - TEMPLATABLE_BYTES(code) - - void encode(RemoteTransmitData *dst, Ts... x) override { - MideaData data = this->code_.is_static() ? MideaData(this->code_.data(), this->code_.size()) - : MideaData(this->code_.value(x...)); - data.finalize(); - MideaProtocol().encode(dst, data); - } -}; - } // namespace esphome::remote_base diff --git a/esphome/components/remote_base/mirage_protocol.cpp b/esphome/components/remote_base/mirage_protocol.cpp index 23c6bd21e8..099388f8ec 100644 --- a/esphome/components/remote_base/mirage_protocol.cpp +++ b/esphome/components/remote_base/mirage_protocol.cpp @@ -1,7 +1,10 @@ #include "mirage_protocol.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include + namespace esphome::remote_base { ESPHOME_LOG_TAG(TAG, "remote.mirage"); @@ -17,8 +20,12 @@ constexpr unsigned int MIRAGE_IR_PACKET_BIT_SIZE = 120; constexpr size_t MIRAGE_MAX_DATA_BYTES = (MIRAGE_IR_PACKET_BIT_SIZE / 8); void MirageProtocol::encode(RemoteTransmitData *dst, const uint8_t *data, size_t len) { + // data may be a PROGMEM table, so log a RAM copy + uint8_t log_buf[MIRAGE_MAX_DATA_BYTES]; + const size_t log_len = std::min(len, MIRAGE_MAX_DATA_BYTES); + progmem_memcpy(log_buf, data, log_len); char hex_buf[format_hex_pretty_size(MIRAGE_MAX_DATA_BYTES)]; - ESP_LOGI(TAG, "Transmit Mirage: %s", format_hex_pretty_to(hex_buf, data, len)); + ESP_LOGI(TAG, "Transmit Mirage: %s", format_hex_pretty_to(hex_buf, log_buf, log_len)); dst->set_carrier_frequency(38000); dst->reserve(5 + ((len + 1) * 2)); dst->mark(HEADER_MARK_US); @@ -26,7 +33,7 @@ void MirageProtocol::encode(RemoteTransmitData *dst, const uint8_t *data, size_t dst->mark(BIT_MARK_US); uint8_t checksum = 0; for (size_t i = 0; i < len; i++) { - const uint8_t item = data[i]; + const uint8_t item = progmem_read_byte(data + i); this->encode_byte_(dst, item); checksum += (item >> 4) + (item & 0xF); } diff --git a/esphome/components/remote_base/mirage_protocol.h b/esphome/components/remote_base/mirage_protocol.h index 923d412e84..ae6ed0a8e9 100644 --- a/esphome/components/remote_base/mirage_protocol.h +++ b/esphome/components/remote_base/mirage_protocol.h @@ -16,7 +16,11 @@ class MirageProtocol : public RemoteProtocol { void encode(RemoteTransmitData *dst, const MirageData &data) { this->encode(dst, data.data.data(), data.data.size()); } + /// data may be a PROGMEM table void encode(RemoteTransmitData *dst, const uint8_t *data, size_t len); + void encode(RemoteTransmitData *dst, const std::vector &data) { + this->encode(dst, data.data(), data.size()); + } optional decode(RemoteReceiveData src); void dump(const MirageData &data); @@ -26,13 +30,4 @@ class MirageProtocol : public RemoteProtocol { DECLARE_REMOTE_PROTOCOL(Mirage) -template class MirageAction : public RemoteTransmitterActionBase { - public: - TEMPLATABLE_BYTES(code) - - void encode(RemoteTransmitData *dst, Ts... x) override { - this->code_.visit([dst](const uint8_t *data, size_t len) { MirageProtocol().encode(dst, data, len); }, x...); - } -}; - } // namespace esphome::remote_base diff --git a/esphome/components/remote_base/remote_base.cpp b/esphome/components/remote_base/remote_base.cpp index 38db90c97b..3c5291ffb2 100644 --- a/esphome/components/remote_base/remote_base.cpp +++ b/esphome/components/remote_base/remote_base.cpp @@ -182,6 +182,11 @@ bool RemoteTransmitData::set_data_from_base64url(const std::string &base64url) { /* RemoteTransmitterBase */ +RemoteTransmitterBase::TransmitCall RemoteTransmitterBase::transmit() { + this->temp_.reset(); + return TransmitCall(this, this->take_seq_()); +} + void RemoteTransmitterBase::transmit_encoded_(EncodeFn encode, const void *data, uint32_t send_times, uint32_t send_wait) { auto call = this->transmit(); diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index fc2f3a7130..783343c5a2 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -187,10 +187,7 @@ class RemoteTransmitterBase : public RemoteComponentBase { uint16_t seq_; }; - TransmitCall transmit() { - this->temp_.reset(); - return TransmitCall(this, this->take_seq_()); - } + TransmitCall transmit(); template void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) { this->transmit_encoded_(&encode_thunk, &data, send_times, send_wait); @@ -213,6 +210,16 @@ class RemoteTransmitterBase : public RemoteComponentBase { template ProtocolCall protocol_call() { return ProtocolCall(this); } + /// A transmit call whose encode() runs one of Protocol's encoders on its data, for a payload + /// the protocol encodes straight from a pointer and length; the call a generated action builds. + template class EncodeCall : public TransmitCall { + public: + explicit EncodeCall(TransmitCall call) : TransmitCall(call) {} + template void encode(const Args &...args) { Protocol().encode(this->get_data(), args...); } + }; + template EncodeCall encode_call() { + return EncodeCall(this->transmit()); + } protected: using EncodeFn = void (*)(RemoteTransmitData *dst, const void *data); diff --git a/tests/component_tests/remote_transmitter/__init__.py b/tests/component_tests/remote_transmitter/__init__.py index 7b2d567ec7..da300b47b1 100644 --- a/tests/component_tests/remote_transmitter/__init__.py +++ b/tests/component_tests/remote_transmitter/__init__.py @@ -3,7 +3,7 @@ import re _PROTOCOL_CALL_BODY = re.compile( - r"auto apply_call = \S+->protocol_call\(\);\n(.*?)apply_call\.perform\(\);", + r"auto apply_call = \S+->(?:protocol|encode)_call\(\);\n(.*?)apply_call\.perform\(\);", re.DOTALL, ) diff --git a/tests/component_tests/remote_transmitter/config/byte_payloads.yaml b/tests/component_tests/remote_transmitter/config/byte_payloads.yaml new file mode 100644 index 0000000000..41eefdad7f --- /dev/null +++ b/tests/component_tests/remote_transmitter/config/byte_payloads.yaml @@ -0,0 +1,33 @@ +esphome: + name: test + +esp8266: + board: esp01_1m + +remote_transmitter: + pin: GPIO4 + carrier_duty_percent: 50% + +button: + - platform: template + name: ABBWelcome fixed id + on_press: + - remote_transmitter.transmit_abbwelcome: + source_address: 0x123456 + destination_address: 0x56789A + three_byte_address: true + message_type: 0x01 + message_id: 0x42 + data: [0x10] + - platform: template + name: AEHA lambda + on_press: + - remote_transmitter.transmit_aeha: + address: !lambda return 0x8008; + carrier_frequency: 36700Hz + data: !lambda return {0x00, 0x02}; + - platform: template + name: Midea lambda + on_press: + - remote_transmitter.transmit_midea: + code: !lambda return {0xA2, 0x08, 0xFF, 0xFF, 0xFF}; diff --git a/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py b/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py index a653b5137e..ea1dbcf294 100644 --- a/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py +++ b/tests/component_tests/remote_transmitter/test_abbwelcome_payload_tables.py @@ -9,13 +9,17 @@ 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.""" + """Equal data shares one table, empty data is a null table, lambdas are called inline.""" 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 + assert main_cpp.count(f"apply_call.data.set_data({tables[0][0]}, 3);") == 2 + # Empty data is a null table; missing data leaves the default empty payload + assert main_cpp.count("apply_call.data.set_data(nullptr, 0);") == 1 + assert ( + main_cpp.count("apply_call.data.set_data([]() -> std::vector {") == 1 + ) + assert "std::vector{" not in main_cpp diff --git a/tests/component_tests/remote_transmitter/test_byte_payloads.py b/tests/component_tests/remote_transmitter/test_byte_payloads.py new file mode 100644 index 0000000000..5bdd60cae0 --- /dev/null +++ b/tests/component_tests/remote_transmitter/test_byte_payloads.py @@ -0,0 +1,33 @@ +"""The byte payload transmit actions as generated code.""" + +from collections.abc import Callable +from pathlib import Path + +from tests.component_tests.remote_transmitter import protocol_call_bodies + + +def test_byte_payloads( + generate_main: Callable[[str | Path], str], + component_config_path: Callable[[str], Path], +) -> None: + main_cpp = generate_main(component_config_path("byte_payloads.yaml")) + abb, aeha, midea = protocol_call_bodies(main_cpp) + # The address length goes first, a configured id turns the automatic one off + assert abb.index("set_three_byte_address(true);") < abb.index("set_source_address(") + assert abb.index("auto_message_id = true;") < abb.index( + "set_fixed_message_id(0x42);" + ) + assert abb.rstrip().endswith("apply_call.data.finalize();") + # The carrier is set before the payload is encoded + assert aeha.index("set_carrier_frequency(36700);") < aeha.index( + "apply_call.encode(" + ) + assert ( + "apply_call.encode(static_cast(0x8008), []() -> std::vector {" + in aeha + ) + assert ( + "apply_call.data = remote_base::MideaData([]() -> std::vector {" + in midea + ) + assert midea.rstrip().endswith("apply_call.data.finalize();") diff --git a/tests/component_tests/remote_transmitter/test_ir_codes.py b/tests/component_tests/remote_transmitter/test_ir_codes.py index 9be34941b6..8d08e52ffd 100644 --- a/tests/component_tests/remote_transmitter/test_ir_codes.py +++ b/tests/component_tests/remote_transmitter/test_ir_codes.py @@ -1,4 +1,4 @@ -"""Constant IR byte codes live in shared PROGMEM tables; lambdas stay lambdas.""" +"""Constant IR byte codes are encoded from shared PROGMEM tables; lambdas are called inline.""" from collections.abc import Callable from pathlib import Path @@ -16,9 +16,10 @@ def test_ir_codes_use_shared_progmem_tables( ) 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 main_cpp.count(f"apply_call.data = remote_base::MideaData({midea}, 5);") == 2 + assert re.search(r"apply_call\.encode\(0x8008, remote_base_code\w*, 6\);", main_cpp) + assert re.search(r"apply_call\.encode\(remote_base_code\w*, 13\);", main_cpp) + assert re.search(r"apply_call\.encode\(remote_base_code\w*, 14\);", main_cpp) + assert "apply_call.encode([]() -> std::vector {" in main_cpp + # Constant codes are read from the table, never copied into a vector assert "std::vector{" not in main_cpp diff --git a/tests/components/remote_base/ir_code_encoders_test.cpp b/tests/components/remote_base/ir_code_encoders_test.cpp index bffea25864..b003318a1f 100644 --- a/tests/components/remote_base/ir_code_encoders_test.cpp +++ b/tests/components/remote_base/ir_code_encoders_test.cpp @@ -62,27 +62,15 @@ RawTimings with_trailing_space(RawTimings timings) { return timings; } -template RawTimings encode(A &action) { - RemoteTransmitData dst; - action.encode(&dst); - return dst.get_data(); -} - -// Exposes the protected encode() of an action for testing -template class Open : public A { - public: - using A::encode; -}; - -// A constant and a lambda code must both encode to the golden timings -template -void expect_static_and_lambda_match(const uint8_t *code, size_t len, std::vector (*lambda)(), - const RawTimings &golden) { - Open fixed, from_lambda; - fixed.set_code_static(code, len); - from_lambda.set_code_template(lambda); - EXPECT_EQ(encode(fixed), golden); - EXPECT_EQ(encode(from_lambda), golden); +// A constant table and a lambda's vector take the two encoder overloads generated actions call +template +void expect_pointer_and_vector_match(const uint8_t *code, size_t len, const std::vector &bytes, + const RawTimings &golden, const Lead &...lead) { + RemoteTransmitData fixed, from_vector; + P().encode(&fixed, lead..., code, len); + P().encode(&from_vector, lead..., bytes); + EXPECT_EQ(fixed.get_data(), golden); + EXPECT_EQ(from_vector.get_data(), golden); } template void expect_decodes_to(const RawTimings &timings, const std::vector &code) { @@ -94,25 +82,16 @@ template void expect_decodes_to(const RawTimings &timings, const std } // namespace -TEST(IrCodeEncodersTest, HaierStaticAndLambdaMatchGolden) { - expect_static_and_lambda_match>(HAIER_CODE, sizeof(HAIER_CODE), haier_code, HAIER_GOLDEN); +TEST(IrCodeEncodersTest, HaierPointerAndVectorMatchGolden) { + expect_pointer_and_vector_match(HAIER_CODE, sizeof(HAIER_CODE), haier_code(), HAIER_GOLDEN); } -TEST(IrCodeEncodersTest, MirageStaticAndLambdaMatchGolden) { - expect_static_and_lambda_match>(MIRAGE_CODE, sizeof(MIRAGE_CODE), mirage_code, MIRAGE_GOLDEN); +TEST(IrCodeEncodersTest, MiragePointerAndVectorMatchGolden) { + expect_pointer_and_vector_match(MIRAGE_CODE, sizeof(MIRAGE_CODE), mirage_code(), MIRAGE_GOLDEN); } -TEST(IrCodeEncodersTest, AehaStaticAndLambdaMatchGolden) { - Open> fixed, lambda; - for (auto *action : {&fixed, &lambda}) { - // TemplatableFn fields take functions, not raw constants - action->set_address([]() -> uint16_t { return AEHA_ADDRESS; }); - action->set_carrier_frequency([]() -> uint32_t { return 38000; }); - } - fixed.set_data_static(AEHA_CODE, sizeof(AEHA_CODE)); - lambda.set_data_template(aeha_code); - EXPECT_EQ(encode(fixed), AEHA_GOLDEN); - EXPECT_EQ(encode(lambda), AEHA_GOLDEN); +TEST(IrCodeEncodersTest, AehaPointerAndVectorMatchGolden) { + expect_pointer_and_vector_match(AEHA_CODE, sizeof(AEHA_CODE), aeha_code(), AEHA_GOLDEN, AEHA_ADDRESS); } TEST(IrCodeEncodersTest, StructEncodersMatchGolden) {