mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[remote_base] Generate the byte payload transmit actions with no C++ class (#20568)
This commit is contained in:
@@ -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_<name>`` with no C++ action class.
|
||||
|
||||
One generated function fills ``<protocol>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
|
||||
|
||||
@@ -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 <array>
|
||||
@@ -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<uint8_t>(std::min<size_t>(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<uint8_t> &data) { this->set_data(data.data(), data.size()); }
|
||||
std::vector<uint8_t> get_data() const {
|
||||
@@ -226,30 +233,4 @@ class ABBWelcomeBinarySensor : public RemoteReceiverBinarySensorBase {
|
||||
using ABBWelcomeTrigger = RemoteReceiverTrigger<ABBWelcomeProtocol>;
|
||||
using ABBWelcomeDumper = RemoteReceiverDumper<ABBWelcomeProtocol>;
|
||||
|
||||
template<typename... Ts> class ABBWelcomeAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
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<MAX_DATA_LENGTH>(
|
||||
[&data](const uint8_t *bytes, size_t len) { data.set_data(bytes, len); }, x...);
|
||||
data.finalize();
|
||||
ABBWelcomeProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace esphome::remote_base
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "aeha_protocol.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -18,7 +18,11 @@ class AEHAProtocol : public RemoteProtocol<AEHAData> {
|
||||
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<uint8_t> &data) {
|
||||
this->encode(dst, address, data.data(), data.size());
|
||||
}
|
||||
optional<AEHAData> decode(RemoteReceiveData src);
|
||||
void dump(const AEHAData &data);
|
||||
|
||||
@@ -28,18 +32,4 @@ class AEHAProtocol : public RemoteProtocol<AEHAData> {
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(AEHA)
|
||||
|
||||
template<typename... Ts> class AEHAAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@ struct HaierData {
|
||||
class HaierProtocol : public RemoteProtocol<HaierData> {
|
||||
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<uint8_t> &data) {
|
||||
this->encode(dst, data.data(), data.size());
|
||||
}
|
||||
optional<HaierData> decode(RemoteReceiveData src);
|
||||
void dump(const HaierData &data);
|
||||
|
||||
@@ -24,13 +28,4 @@ class HaierProtocol : public RemoteProtocol<HaierData> {
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Haier)
|
||||
|
||||
template<typename... Ts> class HaierAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
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
|
||||
|
||||
@@ -78,16 +78,4 @@ class MideaProtocol : public RemoteProtocol<MideaData> {
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Midea)
|
||||
|
||||
template<typename... Ts> class MideaAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
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
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "mirage_protocol.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ class MirageProtocol : public RemoteProtocol<MirageData> {
|
||||
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<uint8_t> &data) {
|
||||
this->encode(dst, data.data(), data.size());
|
||||
}
|
||||
optional<MirageData> decode(RemoteReceiveData src);
|
||||
void dump(const MirageData &data);
|
||||
|
||||
@@ -26,13 +30,4 @@ class MirageProtocol : public RemoteProtocol<MirageData> {
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Mirage)
|
||||
|
||||
template<typename... Ts> class MirageAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<RemoteProtocolEncoder Protocol>
|
||||
void transmit(const typename Protocol::ProtocolData &data, uint32_t send_times = 1, uint32_t send_wait = 0) {
|
||||
this->transmit_encoded_(&encode_thunk<Protocol>, &data, send_times, send_wait);
|
||||
@@ -213,6 +210,16 @@ class RemoteTransmitterBase : public RemoteComponentBase {
|
||||
template<RemoteProtocolEncoder Protocol> ProtocolCall<Protocol> protocol_call() {
|
||||
return ProtocolCall<Protocol>(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<RemoteProtocolEncoder Protocol> class EncodeCall : public TransmitCall {
|
||||
public:
|
||||
explicit EncodeCall(TransmitCall call) : TransmitCall(call) {}
|
||||
template<typename... Args> void encode(const Args &...args) { Protocol().encode(this->get_data(), args...); }
|
||||
};
|
||||
template<RemoteProtocolEncoder Protocol> EncodeCall<Protocol> encode_call() {
|
||||
return EncodeCall<Protocol>(this->transmit());
|
||||
}
|
||||
|
||||
protected:
|
||||
using EncodeFn = void (*)(RemoteTransmitData *dst, const void *data);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import re
|
||||
|
||||
_PROTOCOL_CALL_BODY = re.compile(
|
||||
r"auto apply_call = \S+->protocol_call<remote_base::\w+>\(\);\n(.*?)apply_call\.perform\(\);",
|
||||
r"auto apply_call = \S+->(?:protocol|encode)_call<remote_base::\w+>\(\);\n(.*?)apply_call\.perform\(\);",
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
@@ -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};
|
||||
@@ -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<uint8_t> {") == 1
|
||||
)
|
||||
assert "std::vector<uint8_t>{" not in main_cpp
|
||||
|
||||
@@ -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<uint16_t>(0x8008), []() -> std::vector<uint8_t> {"
|
||||
in aeha
|
||||
)
|
||||
assert (
|
||||
"apply_call.data = remote_base::MideaData([]() -> std::vector<uint8_t> {"
|
||||
in midea
|
||||
)
|
||||
assert midea.rstrip().endswith("apply_call.data.finalize();")
|
||||
@@ -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<uint8_t> {" in main_cpp
|
||||
# Constant codes are read from the table, never copied into a vector
|
||||
assert "std::vector<uint8_t>{" not in main_cpp
|
||||
|
||||
@@ -62,27 +62,15 @@ RawTimings with_trailing_space(RawTimings timings) {
|
||||
return timings;
|
||||
}
|
||||
|
||||
template<typename A> RawTimings encode(A &action) {
|
||||
RemoteTransmitData dst;
|
||||
action.encode(&dst);
|
||||
return dst.get_data();
|
||||
}
|
||||
|
||||
// Exposes the protected encode() of an action for testing
|
||||
template<typename A> class Open : public A {
|
||||
public:
|
||||
using A::encode;
|
||||
};
|
||||
|
||||
// A constant and a lambda code must both encode to the golden timings
|
||||
template<typename A>
|
||||
void expect_static_and_lambda_match(const uint8_t *code, size_t len, std::vector<uint8_t> (*lambda)(),
|
||||
const RawTimings &golden) {
|
||||
Open<A> 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<typename P, typename... Lead>
|
||||
void expect_pointer_and_vector_match(const uint8_t *code, size_t len, const std::vector<uint8_t> &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<typename P> void expect_decodes_to(const RawTimings &timings, const std::vector<uint8_t> &code) {
|
||||
@@ -94,25 +82,16 @@ template<typename P> void expect_decodes_to(const RawTimings &timings, const std
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(IrCodeEncodersTest, HaierStaticAndLambdaMatchGolden) {
|
||||
expect_static_and_lambda_match<HaierAction<>>(HAIER_CODE, sizeof(HAIER_CODE), haier_code, HAIER_GOLDEN);
|
||||
TEST(IrCodeEncodersTest, HaierPointerAndVectorMatchGolden) {
|
||||
expect_pointer_and_vector_match<HaierProtocol>(HAIER_CODE, sizeof(HAIER_CODE), haier_code(), HAIER_GOLDEN);
|
||||
}
|
||||
|
||||
TEST(IrCodeEncodersTest, MirageStaticAndLambdaMatchGolden) {
|
||||
expect_static_and_lambda_match<MirageAction<>>(MIRAGE_CODE, sizeof(MIRAGE_CODE), mirage_code, MIRAGE_GOLDEN);
|
||||
TEST(IrCodeEncodersTest, MiragePointerAndVectorMatchGolden) {
|
||||
expect_pointer_and_vector_match<MirageProtocol>(MIRAGE_CODE, sizeof(MIRAGE_CODE), mirage_code(), MIRAGE_GOLDEN);
|
||||
}
|
||||
|
||||
TEST(IrCodeEncodersTest, AehaStaticAndLambdaMatchGolden) {
|
||||
Open<AEHAAction<>> 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<AEHAProtocol>(AEHA_CODE, sizeof(AEHA_CODE), aeha_code(), AEHA_GOLDEN, AEHA_ADDRESS);
|
||||
}
|
||||
|
||||
TEST(IrCodeEncodersTest, StructEncodersMatchGolden) {
|
||||
|
||||
Reference in New Issue
Block a user