[remote_base] Generate transmit_pronto and transmit_rc6 with no C++ class (#20564)

This commit is contained in:
J. Nick Koston
2026-10-12 07:48:02 -04:00
committed by GitHub
parent 698988669d
commit 9acc07a973
5 changed files with 104 additions and 50 deletions
+35 -18
View File
@@ -308,26 +308,38 @@ def register_action(
return decorator
ProtocolField = (
tuple[str, str, SafeExpType] | automation.ApplyField | automation.ApplyCall
)
def _protocol_field(
field: ProtocolField,
) -> automation.ApplyField | automation.ApplyCall:
if not isinstance(field, tuple):
return field
key, member, type_ = field
return automation.ApplyField(key, f"data.{member} = {{}}", type_)
def register_protocol_action(
name: str,
protocol: str,
schema: cv.Schema,
*fields: tuple[str, str, SafeExpType],
*fields: ProtocolField,
validate: Callable[[ConfigType], ConfigType] | None = None,
) -> 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``.
and sends it; the action is a core ``ApplyAction``. ``ApplyField`` and ``ApplyCall`` pass through
and target the call object, whose protocol data is ``data``.
"""
action, schema, validator = _transmit_action(name, schema, validate)
builder = automation.apply_action_builder(
action,
schema,
*(
automation.ApplyField(key, f"data.{member} = {{}}", type_)
for key, member, type_ in fields
),
*(_protocol_field(field) for field in fields),
automation.ApplyField((CONF_REPEAT, CONF_TIMES), "set_send_times", cg.uint32),
automation.ApplyField(
(CONF_REPEAT, CONF_WAIT_TIME), "set_send_wait", cg.uint32
@@ -1206,7 +1218,7 @@ register_protocol_action(
ProntoData,
ProntoBinarySensor,
ProntoTrigger,
ProntoAction,
_,
ProntoDumper,
) = declare_protocol("Pronto")
PRONTO_SCHEMA = cv.Schema(
@@ -1240,10 +1252,10 @@ def pronto_dumper(var, config):
pass
@register_action("pronto", ProntoAction, PRONTO_SCHEMA)
async def pronto_action(var, config, args):
template_ = await cg.templatable(config[CONF_DATA], args, cg.std_string)
cg.add(var.set_data(template_))
# delta only matters when matching a received code
register_protocol_action(
"pronto", "Pronto", PRONTO_SCHEMA, (CONF_DATA, "data", cg.std_string)
)
# Gobox
@@ -1640,7 +1652,7 @@ register_protocol_action(
# RC6
RC6Data, RC6BinarySensor, RC6Trigger, RC6Action, RC6Dumper = declare_protocol("RC6")
RC6Data, RC6BinarySensor, RC6Trigger, _, RC6Dumper = declare_protocol("RC6")
RC6_SCHEMA = cv.Schema(
{
cv.Required(CONF_ADDRESS): cv.hex_uint8_t,
@@ -1674,12 +1686,17 @@ def rc6_dumper(var, config):
pass
@register_action("rc6", RC6Action, RC6_SCHEMA)
async def rc6_action(var, config, args):
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
cg.add(var.set_address(template_))
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
cg.add(var.set_command(template_))
register_protocol_action(
"rc6",
"RC6",
RC6_SCHEMA,
(CONF_ADDRESS, "address", cg.uint8),
(CONF_COMMAND, "command", cg.uint8),
# Each action flips its own toggle bit, starting at 0: the lambda's static is per action
automation.ApplyCall(
"data.toggle = []() {{ static uint8_t toggle; return toggle++; }}()"
),
)
# RC Switch Raw
@@ -37,17 +37,4 @@ class ProntoProtocol : public RemoteProtocol<ProntoData> {
DECLARE_REMOTE_PROTOCOL(Pronto)
template<typename... Ts> class ProntoAction : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(std::string, data)
TEMPLATABLE_VALUE(int, delta)
void encode(RemoteTransmitData *dst, Ts... x) override {
ProntoData data{};
data.data = this->data_.value(x...);
data.delta = this->delta_.value(x...);
ProntoProtocol().encode(dst, data);
}
};
} // namespace esphome::remote_base
@@ -22,23 +22,4 @@ class RC6Protocol : public RemoteProtocol<RC6Data> {
DECLARE_REMOTE_PROTOCOL(RC6)
template<typename... Ts> class RC6Action : public RemoteTransmitterActionBase<Ts...> {
public:
TEMPLATABLE_VALUE(uint8_t, address)
TEMPLATABLE_VALUE(uint8_t, command)
void encode(RemoteTransmitData *dst, Ts... x) {
RC6Data data{};
data.mode = 0;
data.toggle = this->toggle_;
data.address = this->address_.value(x...);
data.command = this->command_.value(x...);
RC6Protocol().encode(dst, data);
this->toggle_ = !this->toggle_;
}
protected:
uint8_t toggle_{0};
};
} // namespace esphome::remote_base
@@ -0,0 +1,28 @@
esphome:
name: test
esp8266:
board: esp01_1m
remote_transmitter:
pin: GPIO4
carrier_duty_percent: 50%
button:
- platform: template
name: RC6 one
on_press:
- remote_transmitter.transmit_rc6:
address: 0x00
command: 0x0C
- platform: template
name: RC6 two
on_press:
- remote_transmitter.transmit_rc6:
address: 0x00
command: 0x0D
- platform: template
name: Pronto
on_press:
- remote_transmitter.transmit_pronto:
data: "0000 006D 0001 0000 0015 0016"
@@ -0,0 +1,41 @@
"""transmit_rc6 keeps a toggle bit per action, and transmit_pronto sends its string."""
from collections.abc import Callable
from pathlib import Path
from esphome import automation
import esphome.codegen as cg
from esphome.components.remote_base import _protocol_field
from esphome.const import CONF_TEMPERATURE
from tests.component_tests.remote_transmitter import protocol_call_bodies
def test_rc6_toggle_and_pronto(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("rc6_pronto.yaml"))
bodies = protocol_call_bodies(main_cpp)
assert len(bodies) == 3
toggle = (
"apply_call.data.toggle = []() { static uint8_t toggle; return toggle++; }();"
)
# Each RC6 action has its own generated function, so its own toggle static.
assert toggle in bodies[0]
assert toggle in bodies[1]
assert (
'apply_call.data.data = progmem_string(ESPHOME_F("0000 006D 0001 0000 0015 0016"));'
in bodies[2]
)
assert "delta" not in bodies[2]
def test_apply_objects_pass_through() -> None:
call = automation.ApplyCall("stop()")
assert _protocol_field(call) is call
field = automation.ApplyField(
CONF_TEMPERATURE, "data.set_temperature({})", cg.float_
)
assert _protocol_field(field) is field
plain = _protocol_field((CONF_TEMPERATURE, "temperature", cg.float_))
assert plain.target == "data.temperature = {}"