mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 12:17:23 +00:00
[radio_frequency] Add on_control trigger; ir_rf_proxy driver-agnostic (#16368)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,7 +106,6 @@ void RfProxy::setup() {
|
||||
void RfProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"RF Proxy '%s'\n"
|
||||
" Backend: remote_transmitter/receiver\n"
|
||||
" Supports Transmitter: %s\n"
|
||||
" Supports Receiver: %s",
|
||||
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
|
||||
@@ -124,7 +123,9 @@ void RfProxy::dump_config() {
|
||||
}
|
||||
|
||||
void RfProxy::control(const radio_frequency::RadioFrequencyCall &call) {
|
||||
// RF: no IR carrier modulation
|
||||
// RF: no IR carrier modulation. Any RF front-end coordination (state turnaround, retuning)
|
||||
// happens via the radio_frequency entity's on_control trigger and remote_transmitter's
|
||||
// on_transmit/on_complete triggers — wired up in user YAML.
|
||||
transmit_raw_timings(this->transmitter_, 0, call);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,10 @@ class IrRfProxy : public infrared::Infrared {
|
||||
#endif // USE_IR_RF
|
||||
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
/// RfProxy - Radio Frequency platform implementation using remote_transmitter/receiver as backend
|
||||
/// RfProxy - Radio Frequency platform implementation using remote_transmitter/receiver as backend.
|
||||
/// Driver-agnostic: integration with specific RF front-end chips (CC1101, RFM69, etc.) is done
|
||||
/// in YAML by wiring their actions to `remote_transmitter`'s on_transmit/on_complete triggers and
|
||||
/// to this entity's on_control trigger (see radio_frequency component docs).
|
||||
class RfProxy : public radio_frequency::RadioFrequency {
|
||||
public:
|
||||
RfProxy() = default;
|
||||
|
||||
@@ -35,17 +35,19 @@ def _final_validate(config: ConfigType) -> None:
|
||||
if CONF_REMOTE_TRANSMITTER_ID not in config:
|
||||
return
|
||||
|
||||
transmitter_id = config[CONF_REMOTE_TRANSMITTER_ID]
|
||||
full_config = fv.full_config.get()
|
||||
transmitter_path = full_config.get_path_for_id(transmitter_id)[:-1]
|
||||
transmitter_path = full_config.get_path_for_id(config[CONF_REMOTE_TRANSMITTER_ID])[
|
||||
:-1
|
||||
]
|
||||
transmitter_config = full_config.get_config_for_path(transmitter_path)
|
||||
|
||||
duty_percent = transmitter_config.get(CONF_CARRIER_DUTY_PERCENT)
|
||||
if duty_percent is not None and duty_percent != 100:
|
||||
raise cv.Invalid(
|
||||
f"Transmitter '{transmitter_id}' must have '{CONF_CARRIER_DUTY_PERCENT}' "
|
||||
"set to 100% for RF transmission. Dedicated RF hardware handles modulation; "
|
||||
"applying a carrier duty cycle would corrupt the signal"
|
||||
f"Transmitter '{config[CONF_REMOTE_TRANSMITTER_ID]}' must have "
|
||||
f"'{CONF_CARRIER_DUTY_PERCENT}' set to 100% for RF transmission. "
|
||||
"Dedicated RF hardware handles modulation; applying a carrier duty cycle "
|
||||
"would corrupt the signal"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ breaking changes policy. Use at your own risk.
|
||||
Once the API is considered stable, this warning will be removed.
|
||||
"""
|
||||
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_ON_CONTROL
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import queue_entity_register, setup_entity
|
||||
from esphome.coroutine import CoroPriority
|
||||
@@ -42,6 +43,7 @@ def radio_frequency_schema(class_: type[cg.MockObjClass]) -> cv.Schema:
|
||||
return entity_schema.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
cv.Optional(CONF_ON_CONTROL): automation.validate_automation({}),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -59,6 +61,11 @@ async def register_radio_frequency(var: cg.Pvariable, config: ConfigType) -> Non
|
||||
await setup_radio_frequency_core_(var, config)
|
||||
CORE.register_platform_component("radio_frequency", var)
|
||||
|
||||
for conf in config.get(CONF_ON_CONTROL, []):
|
||||
await automation.build_callback_automation(
|
||||
var, "add_on_control_callback", [(RadioFrequencyCall, "x")], conf
|
||||
)
|
||||
|
||||
|
||||
async def new_radio_frequency(config: ConfigType, *args) -> cg.Pvariable:
|
||||
"""Create a new RadioFrequency instance.
|
||||
|
||||
@@ -54,6 +54,10 @@ RadioFrequencyCall &RadioFrequencyCall::set_repeat_count(uint32_t count) {
|
||||
|
||||
void RadioFrequencyCall::perform() {
|
||||
if (this->parent_ != nullptr) {
|
||||
// Fire any on_control hooks (user-wired automations) before handing off to
|
||||
// the platform-specific control() — gives users a chance to react to call
|
||||
// parameters (e.g. retune an external RF front-end based on call.get_frequency()).
|
||||
this->parent_->control_callback_.call(*this);
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,15 @@ class RadioFrequency : public Component, public EntityBase, public remote_base::
|
||||
this->receive_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
/// Add a callback to invoke when a transmit call is made on this entity.
|
||||
/// Fires before the platform-specific control() runs, with the call object
|
||||
/// (containing frequency, modulation, repeat count, etc.). Used by the
|
||||
/// `on_control` YAML trigger so users can wire any RF front-end driver
|
||||
/// (CC1101, RFM69, custom) to react to per-call parameters.
|
||||
template<typename F> void add_on_control_callback(F &&callback) {
|
||||
this->control_callback_.add(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class RadioFrequencyCall;
|
||||
|
||||
@@ -182,6 +191,8 @@ class RadioFrequency : public Component, public EntityBase, public remote_base::
|
||||
|
||||
// Callback manager for receive events (lazy: saves memory when no callbacks registered)
|
||||
LazyCallbackManager<void(remote_base::RemoteReceiveData)> receive_callback_;
|
||||
// Callback manager for on_control trigger (lazy: same memory savings)
|
||||
LazyCallbackManager<void(const RadioFrequencyCall &)> control_callback_;
|
||||
};
|
||||
|
||||
} // namespace esphome::radio_frequency
|
||||
|
||||
50
tests/components/ir_rf_proxy/common-cc1101.yaml
Normal file
50
tests/components/ir_rf_proxy/common-cc1101.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
cc1101:
|
||||
id: cc1101_radio
|
||||
cs_pin: ${cs_pin}
|
||||
frequency: 433.92MHz
|
||||
modulation_type: ASK/OOK
|
||||
output_power: 10
|
||||
|
||||
# Dual-pin wiring (recommended by the CC1101 docs):
|
||||
# CC1101 GDO0 → ${gdo0_pin} (remote_transmitter)
|
||||
# CC1101 GDO2 → ${gdo2_pin} (remote_receiver)
|
||||
remote_transmitter:
|
||||
id: rf_tx
|
||||
pin: ${gdo0_pin}
|
||||
carrier_duty_percent: 100%
|
||||
# Switch the chip into TX state for the duration of each transmission and back to RX
|
||||
# afterwards. Driver-agnostic: any RF front-end with begin_tx/begin_rx-style actions
|
||||
# can be wired this way.
|
||||
on_transmit:
|
||||
then:
|
||||
- cc1101.begin_tx: cc1101_radio
|
||||
on_complete:
|
||||
then:
|
||||
- cc1101.begin_rx: cc1101_radio
|
||||
|
||||
remote_receiver:
|
||||
id: rf_rx
|
||||
pin: ${gdo2_pin}
|
||||
|
||||
radio_frequency:
|
||||
- platform: ir_rf_proxy
|
||||
id: rf_proxy_cc1101_tx
|
||||
name: "CC1101 RF Transmitter"
|
||||
frequency: 433.92MHz
|
||||
remote_transmitter_id: rf_tx
|
||||
# Optional: retune the CC1101 per-transmit when the API request specifies a
|
||||
# different carrier frequency. Demonstrates the on_control trigger.
|
||||
on_control:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: "return x.get_frequency().has_value() && *x.get_frequency() > 0;"
|
||||
then:
|
||||
- cc1101.set_frequency:
|
||||
id: cc1101_radio
|
||||
value: !lambda "return *x.get_frequency();"
|
||||
- platform: ir_rf_proxy
|
||||
id: rf_proxy_cc1101_rx
|
||||
name: "CC1101 RF Receiver"
|
||||
frequency: 433.92MHz
|
||||
remote_receiver_id: rf_rx
|
||||
9
tests/components/ir_rf_proxy/test-cc1101.esp32-idf.yaml
Normal file
9
tests/components/ir_rf_proxy/test-cc1101.esp32-idf.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO5
|
||||
gdo0_pin: GPIO4
|
||||
gdo2_pin: GPIO16
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
|
||||
cc1101: !include common-cc1101.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
substitutions:
|
||||
cs_pin: GPIO5
|
||||
gdo0_pin: GPIO4
|
||||
gdo2_pin: GPIO16
|
||||
|
||||
packages:
|
||||
common: !include common.yaml
|
||||
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
|
||||
cc1101: !include common-cc1101.yaml
|
||||
Reference in New Issue
Block a user