mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 11:26:39 +00:00
[remote_base] Keep RC switch protocols in flash (#20081)
This commit is contained in:
@@ -1538,9 +1538,7 @@ def validate_rc_switch_raw_code(value):
|
||||
return value
|
||||
|
||||
|
||||
def build_rc_switch_protocol(config):
|
||||
if isinstance(config, int):
|
||||
return rc_switch_protocol(config)
|
||||
def build_custom_rc_switch_protocol(config: ConfigType) -> MockObj:
|
||||
pl = config[CONF_PULSE_LENGTH]
|
||||
return RCSwitchBase(
|
||||
config[CONF_SYNC][0] * pl,
|
||||
@@ -1553,6 +1551,24 @@ def build_rc_switch_protocol(config):
|
||||
)
|
||||
|
||||
|
||||
def rc_switch_protocol_in_flash(config: int | ConfigType) -> MockObj:
|
||||
"""Pointer to the protocol in flash: a built-in table entry or a shared custom table."""
|
||||
if isinstance(config, int):
|
||||
return cg.RawExpression(f"&{RC_SWITCH_PROTOCOLS}[{config}]")
|
||||
return cg.shared_progmem_array(
|
||||
"rc_switch_custom_protocol",
|
||||
RCSwitchBase,
|
||||
[build_custom_rc_switch_protocol(config)],
|
||||
)
|
||||
|
||||
|
||||
def rc_switch_protocol_value(config: int | ConfigType) -> MockObj:
|
||||
"""RAM copy of a constant protocol for the transmit actions, read from its flash table."""
|
||||
if isinstance(config, int):
|
||||
return rc_switch_protocol(config)
|
||||
return rc_switch_protocol_copy(rc_switch_protocol_in_flash(config))
|
||||
|
||||
|
||||
RC_SWITCH_RAW_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CODE): validate_rc_switch_raw_code,
|
||||
@@ -1628,6 +1644,8 @@ RC_SWITCH_TRANSMITTER = cv.Schema(
|
||||
)
|
||||
|
||||
rc_switch_protocol = ns.rc_switch_protocol
|
||||
rc_switch_protocol_copy = ns.rc_switch_protocol_copy
|
||||
RC_SWITCH_PROTOCOLS = ns.RC_SWITCH_PROTOCOLS
|
||||
RCSwitchData = ns.struct("RCSwitchData")
|
||||
RCSwitchBase = ns.class_("RCSwitchBase")
|
||||
RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger)
|
||||
@@ -1642,7 +1660,7 @@ RCSwitchRawReceiver = ns.class_("RCSwitchRawReceiver", RemoteReceiverBinarySenso
|
||||
|
||||
@register_binary_sensor("rc_switch_raw", RCSwitchRawReceiver, RC_SWITCH_RAW_SCHEMA)
|
||||
def rc_switch_raw_binary_sensor(var, config):
|
||||
cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_code(config[CONF_CODE]))
|
||||
|
||||
|
||||
@@ -1653,7 +1671,7 @@ def rc_switch_raw_binary_sensor(var, config):
|
||||
)
|
||||
async def rc_switch_raw_action(var, config, args):
|
||||
proto = await cg.templatable(
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
|
||||
)
|
||||
cg.add(var.set_protocol(proto))
|
||||
cg.add(var.set_code(await cg.templatable(config[CONF_CODE], args, cg.std_string)))
|
||||
@@ -1663,7 +1681,7 @@ async def rc_switch_raw_action(var, config, args):
|
||||
"rc_switch_type_a", RCSwitchRawReceiver, RC_SWITCH_TYPE_A_SCHEMA
|
||||
)
|
||||
def rc_switch_type_a_binary_sensor(var, config):
|
||||
cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_type_a(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE]))
|
||||
|
||||
|
||||
@@ -1674,7 +1692,7 @@ def rc_switch_type_a_binary_sensor(var, config):
|
||||
)
|
||||
async def rc_switch_type_a_action(var, config, args):
|
||||
proto = await cg.templatable(
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
|
||||
)
|
||||
cg.add(var.set_protocol(proto))
|
||||
cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string)))
|
||||
@@ -1688,7 +1706,7 @@ async def rc_switch_type_a_action(var, config, args):
|
||||
"rc_switch_type_b", RCSwitchRawReceiver, RC_SWITCH_TYPE_B_SCHEMA
|
||||
)
|
||||
def rc_switch_type_b_binary_sensor(var, config):
|
||||
cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
|
||||
cg.add(
|
||||
var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE])
|
||||
)
|
||||
@@ -1701,7 +1719,7 @@ def rc_switch_type_b_binary_sensor(var, config):
|
||||
)
|
||||
async def rc_switch_type_b_action(var, config, args):
|
||||
proto = await cg.templatable(
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
|
||||
)
|
||||
cg.add(var.set_protocol(proto))
|
||||
cg.add(var.set_address(await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)))
|
||||
@@ -1713,7 +1731,7 @@ async def rc_switch_type_b_action(var, config, args):
|
||||
"rc_switch_type_c", RCSwitchRawReceiver, RC_SWITCH_TYPE_C_SCHEMA
|
||||
)
|
||||
def rc_switch_type_c_binary_sensor(var, config):
|
||||
cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
|
||||
cg.add(
|
||||
var.set_type_c(
|
||||
config[CONF_FAMILY],
|
||||
@@ -1731,7 +1749,7 @@ def rc_switch_type_c_binary_sensor(var, config):
|
||||
)
|
||||
async def rc_switch_type_c_action(var, config, args):
|
||||
proto = await cg.templatable(
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
|
||||
)
|
||||
cg.add(var.set_protocol(proto))
|
||||
cg.add(
|
||||
@@ -1748,7 +1766,7 @@ async def rc_switch_type_c_action(var, config, args):
|
||||
RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER),
|
||||
)
|
||||
def rc_switch_type_d_binary_sensor(var, config):
|
||||
cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_protocol(rc_switch_protocol_in_flash(config[CONF_PROTOCOL])))
|
||||
cg.add(var.set_type_d(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE]))
|
||||
|
||||
|
||||
@@ -1759,7 +1777,7 @@ def rc_switch_type_d_binary_sensor(var, config):
|
||||
)
|
||||
async def rc_switch_type_d_action(var, config, args):
|
||||
proto = await cg.templatable(
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol
|
||||
config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=rc_switch_protocol_value
|
||||
)
|
||||
cg.add(var.set_protocol(proto))
|
||||
cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string)))
|
||||
|
||||
@@ -9,12 +9,16 @@ namespace esphome::remote_base {
|
||||
static const char *const TAG = "remote.rc_switch";
|
||||
|
||||
RCSwitchBase rc_switch_protocol(uint8_t index) {
|
||||
RCSwitchBase protocol;
|
||||
// entry 0 is the all-zero protocol, so an out of range index from a lambda transmits nothing
|
||||
if (index >= std::size(RC_SWITCH_PROTOCOLS))
|
||||
index = 0;
|
||||
progmem_memcpy(&protocol, &RC_SWITCH_PROTOCOLS[index], sizeof(protocol));
|
||||
return protocol;
|
||||
return rc_switch_protocol_copy(&RC_SWITCH_PROTOCOLS[index]);
|
||||
}
|
||||
|
||||
RCSwitchBase rc_switch_protocol_copy(const RCSwitchBase *protocol) {
|
||||
RCSwitchBase copy;
|
||||
progmem_memcpy(©, protocol, sizeof(copy));
|
||||
return copy;
|
||||
}
|
||||
|
||||
void RCSwitchBase::one(RemoteTransmitData *dst) const {
|
||||
@@ -232,7 +236,7 @@ uint64_t decode_binary_string_mask(const std::string &data) {
|
||||
bool RCSwitchRawReceiver::matches(RemoteReceiveData src) {
|
||||
uint64_t decoded_code;
|
||||
uint8_t decoded_nbits;
|
||||
if (!this->protocol_.decode(src, &decoded_code, &decoded_nbits))
|
||||
if (!this->protocol_->decode(src, &decoded_code, &decoded_nbits))
|
||||
return false;
|
||||
|
||||
return decoded_nbits == this->nbits_ && (decoded_code & this->mask_) == (this->code_ & this->mask_);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "remote_base.h"
|
||||
|
||||
@@ -66,6 +68,9 @@ class RCSwitchBase {
|
||||
uint32_t one_high_{};
|
||||
uint32_t one_low_{};
|
||||
uint32_t inverted_{}; // bool widened so every field is a word: the table is read from flash
|
||||
|
||||
// A bool here would still pad to 28 bytes, so the size check below alone would not catch it.
|
||||
static_assert(std::is_same_v<decltype(inverted_), uint32_t>, "inverted_ must stay a word for flash reads");
|
||||
};
|
||||
|
||||
// Constant-initialized and kept in flash on every platform. The decoder reads entries in place
|
||||
@@ -87,6 +92,9 @@ inline constexpr RCSwitchBase RC_SWITCH_PROTOCOLS[] PROGMEM = {
|
||||
/// RAM copy of RC_SWITCH_PROTOCOLS[index] (0 when out of range) for the transmit actions and the dumper, made with
|
||||
/// progmem_memcpy so no byte load ever touches the flash table on ESP8266
|
||||
RCSwitchBase rc_switch_protocol(uint8_t index);
|
||||
/// RAM copy of a protocol stored in flash, made with progmem_memcpy (own name: `rc_switch_protocol(0)` stays
|
||||
/// unambiguous)
|
||||
RCSwitchBase rc_switch_protocol_copy(const RCSwitchBase *protocol);
|
||||
|
||||
uint64_t decode_binary_string(const std::string &data);
|
||||
|
||||
@@ -200,7 +208,8 @@ template<typename... Ts> class RCSwitchTypeDAction : public RemoteTransmitterAct
|
||||
|
||||
class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase {
|
||||
public:
|
||||
void set_protocol(const RCSwitchBase &a_protocol) { this->protocol_ = a_protocol; }
|
||||
/// `protocol` must outlive the receiver: a RC_SWITCH_PROTOCOLS entry or a codegen flash table.
|
||||
void set_protocol(const RCSwitchBase *protocol) { this->protocol_ = protocol; }
|
||||
void set_code(uint64_t code) { this->code_ = code; }
|
||||
void set_code(const std::string &code) {
|
||||
this->code_ = decode_binary_string(code);
|
||||
@@ -228,7 +237,7 @@ class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase {
|
||||
protected:
|
||||
bool matches(RemoteReceiveData src) override;
|
||||
|
||||
RCSwitchBase protocol_;
|
||||
const RCSwitchBase *protocol_{nullptr}; // in flash; decoded in place (word-only fields)
|
||||
uint64_t code_;
|
||||
uint64_t mask_{0xFFFFFFFFFFFFFFFF};
|
||||
uint8_t nbits_;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
remote_transmitter:
|
||||
pin: GPIO4
|
||||
carrier_duty_percent: 100%
|
||||
|
||||
remote_receiver:
|
||||
pin: GPIO5
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: Numbered
|
||||
on_press:
|
||||
- remote_transmitter.transmit_rc_switch_raw:
|
||||
code: '1000100000000000'
|
||||
protocol: 1
|
||||
- platform: template
|
||||
name: Custom A
|
||||
on_press:
|
||||
- remote_transmitter.transmit_rc_switch_raw:
|
||||
code: '1010101010101010'
|
||||
protocol:
|
||||
pulse_length: 320
|
||||
- platform: template
|
||||
name: Custom B
|
||||
on_press:
|
||||
- remote_transmitter.transmit_rc_switch_type_b:
|
||||
address: 4
|
||||
channel: 2
|
||||
state: true
|
||||
protocol:
|
||||
pulse_length: 320
|
||||
- platform: template
|
||||
name: Lambda
|
||||
on_press:
|
||||
- remote_transmitter.transmit_rc_switch_raw:
|
||||
code: '1010101010101011'
|
||||
protocol: !lambda return remote_base::rc_switch_protocol(2);
|
||||
|
||||
binary_sensor:
|
||||
- platform: remote_receiver
|
||||
name: Numbered RX
|
||||
rc_switch_raw:
|
||||
code: '1000100000000000'
|
||||
protocol: 1
|
||||
- platform: remote_receiver
|
||||
name: Custom RX
|
||||
rc_switch_raw:
|
||||
code: '1010101010101010'
|
||||
protocol:
|
||||
pulse_length: 320
|
||||
- platform: template
|
||||
id: rc_switch_custom_protocol
|
||||
@@ -0,0 +1,25 @@
|
||||
"""RC switch protocols are read from flash tables instead of being copied per entity."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_rc_switch_protocols_share_flash_tables(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("rc_switch_protocol.yaml"))
|
||||
|
||||
# One shared table for the identical custom protocols; the user id keeps its name.
|
||||
assert main_cpp.count("static constexpr remote_base::RCSwitchBase") == 1
|
||||
assert (
|
||||
"remote_base::RCSwitchBase rc_switch_custom_protocol_2[] PROGMEM = "
|
||||
"{remote_base::RCSwitchBase(320, 9920, 320, 960, 960, 320, false)};"
|
||||
) in main_cpp
|
||||
assert main_cpp.count("rc_switch_protocol_copy(rc_switch_custom_protocol_2)") == 2
|
||||
# Receivers point into flash instead of holding a copy.
|
||||
assert "->set_protocol(&remote_base::RC_SWITCH_PROTOCOLS[1]);" in main_cpp
|
||||
assert "->set_protocol(rc_switch_custom_protocol_2);" in main_cpp
|
||||
# Numbered and lambda protocols keep their existing paths.
|
||||
assert "return remote_base::rc_switch_protocol(1);" in main_cpp
|
||||
assert "return remote_base::rc_switch_protocol(2);" in main_cpp
|
||||
Reference in New Issue
Block a user