mdehaas
(Marcel de Haas)
May 11, 2026, 9:01am
1
Hi all,
I've been trying to get an RF Proxy working using Googling and asking supposedly intelligent algorithms and I think I'm close but cannot get the last step to work. I have seen data popping up when using the remote_receiver. The entry "RF Proxy" shows up in Home Assistant but the state always end up being unknown and trying to onboard the Novy Cooker Hood results in the message "No radio frequency transmitters are available. Please set up a transmitter first."
Can someone please help me point out where I'm going wrong?
Pinout ESP32 and CC1101. First two columns are on the RF Module.
| CC1101 | PIN | ESP32 | Color |
| ------ | --- | ------ | ------ |
| VCC | 2 | 3.3V | Red |
| GND | 1 | GND | Black |
| SCK | 5 | GPIO18 | Orange |
| MISO | 7 | GPIO19 | Yellow |
| MOSI | 6 | GPIO23 | Purple |
| CSN | 4 | GPIO21 | Green |
| GDO0 | 3 | GPIO16 | Blue |
| GDO2 | 8 | GPIO22 | Brown |
Relevant part of my configuration:
# SPI bus
spi:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
# CC1101
cc1101:
cs_pin: GPIO21
gdo0_pin: GPIO16
frequency: 433.92MHz
modulation_type: ASK/OOK
output_power: 10
symbol_rate: 5000
# RF transmitter
remote_transmitter:
id: rf_tx
pin: GPIO16 # Changed from GPIO17 to match CC1101 GDO0 (table)
carrier_duty_percent: 100%
non_blocking: true
# RF receiver
# remote_receiver:
# id: rf_rx
# pin: GPIO22 # Uncommented to match CC1101 GDO2 (table)
# dump: all
# tolerance: 25%
# filter: 250us
# idle: 4ms
# buffer_size: 2kb
# Infrared
infrared:
- platform: ir_rf_proxy
id: rf_proxy
name: RF Proxy
frequency: 433.92MHz #Docs state: frequency: 433 MHz
remote_transmitter_id: rf_tx
Karosm
(Karosm)
May 11, 2026, 9:56am
2
Why are you using infrared instead of RF?
Also, remove the gdo0_pin from cc1101:
mdehaas
(Marcel de Haas)
May 11, 2026, 10:30am
3
As far as I worked out reading the documentation (IR/RF Proxy - ESPHome - Smart Home Made Simple ) this is the way to create the proxy element.
Removing that line does not change the state of the RF Proxy
Karosm
(Karosm)
May 11, 2026, 10:38am
5
Edit: I had a look at the component api and you are right, it's configured as infrared:
Doesnt make sense, but it's on experimental state so...
There is an example config for the M5 Stack device that uses radio_frequency not infrared...
mdehaas
(Marcel de Haas)
May 11, 2026, 7:27pm
7
Didgeridrew:
radio_frequency
Yeah, I found that to but that component cannot be found. So maybe this is something that will appear in the next release but for now infrared seems to be the correct component. I could not find any usable information searching GitHub repostories...
Karosm
(Karosm)
May 11, 2026, 7:40pm
8
Didgeridrew:
radio_frequency
Interesting. I can't find it on docs, neither on api. Do you have further info about that?
Unfortunately, no. I found that while trying to help on an earlier post... it seems like maybe there's been a miscommunication between either HA v. ESPHome or the docs v. API.
mdehaas
(Marcel de Haas)
May 11, 2026, 8:00pm
10
I see this in the web interface of the ESP. Do I need to investigate ESP any further or is the issue in HA?
Karosm
(Karosm)
May 12, 2026, 9:18am
11
Interesting how it offers you 38kHz carrier even if you set all up as RF transmitter.
Did you try if it works?
makai
May 12, 2026, 11:10am
12
You need to wait for the next major release of esphome (2026.5.0) to get radio_frequency to work with CC1101:
dev β 20260511-cc1101-rf-proxy
opened 05:59AM - 12 May 26 UTC
# What does this implement/fix?
Adds optional CC1101 integration to the `ir_r⦠f_proxy` radio_frequency platform, allowing a CC1101 sub-GHz transceiver to be used as a **programmable** RF front-end for raw-timing transmission and reception. Compared to cheap fixed-frequency 433 MHz modules, this gives users:
- Tunable carrier frequency anywhere in 300-928 MHz (runtime-changeable via `cc1101.set_frequency`, **and via the API on every transmit** β see "Runtime frequency override" below)
- Programmable output power (-30 to +11 dBm)
- Significantly better RX sensitivity and selectivity
- Runtime access to AGC, RSSI, LQI, filter bandwidth, etc.
- **Combined half-duplex TX + RX on a single CC1101 + single shared GPIO pin** β see "Combined TX + RX" below
The CC1101 already supports an "async transparent" OOK mode where GDO0 directly modulates the carrier β this PR wires that mode into the existing `ir_rf_proxy` + `remote_transmitter`/`remote_receiver` stack so the chip becomes a drop-in replacement at the radio layer without any new transmit/receive infrastructure.
### How it works
Adding `cc1101_id` to a `radio_frequency: - platform: ir_rf_proxy` entry:
1. At setup, `RfProxy` defers a call to `cc1101_->begin_rx()` (chip's default idle state)
2. CC1101 is told to stop touching the shared GDO0 pin (see "Pin-sharing fix" below)
3. `remote_transmitter`/`remote_receiver` continue to own GDO0's direction and RMT peripheral routing as usual
4. On TX, the chip is briefly switched to TX state and back to RX after the transmission completes
### Combined TX + RX on a single CC1101
Users can wire one CC1101 to one GPIO pin and configure **two** `radio_frequency` entities β one TX and one RX β both pointing at the same `cc1101_id`. The platform handles half-duplex turnaround transparently:
1. CC1101 sits in RX state (continuously listening for incoming RF)
2. When a transmit request arrives, `RfProxy::control()`:
- Marks `cc1101_->set_tx_busy(true)` so the sibling RX entity drops ghost events generated by the MCU driving the shared pin
- Calls `cc1101_->begin_tx()` to switch the chip into TX state
- Hands off to `remote_transmitter` via the existing transmit helper
- Sums the raw timings Γ repeat count to compute the in-flight transmission duration
- Schedules a named `set_timeout` to return the chip to RX state once RMT is done playing out the symbols
3. Named timeout auto-cancels if a new TX arrives before the timer fires (overlapping transmits are handled correctly)
This works on a single shared GPIO pin (`gdo0_pin` on the CC1101, `pin` on both `remote_transmitter` and `remote_receiver`, all with `allow_other_uses: true`) β a single-pin design is friendlier to GPIO-constrained boards.
### Runtime frequency override
`InfraredRFTransmitRawTimingsRequest.carrier_frequency` is honored when the entity is backed by a CC1101. If the API client sends a transmit request with a non-zero carrier frequency that differs from the chip's current tuning, `RfProxy::control()` retunes the CC1101 before transmitting. Subsequent transmits at the same frequency skip the retune (each one costs ~1.5 ms in PLL calibration). The "last-set" value is initialized from the entity's configured `frequency:` (if any) so the first matching transmit doesn't retune unnecessarily.
This means a single ESPHome device with one CC1101 can transmit on different carrier frequencies (e.g., 315 MHz and 433.92 MHz) just by varying the `carrier_frequency` field in successive API requests β no extra YAML or automations required.
### Pin-sharing fix in CC1101
ESP32's GPIO matrix routes peripheral signals (RMT, in this case) to pins. Calling `gpio_set_direction()` on such a pin **silently detaches it from the peripheral** and reverts it to plain GPIO β meaning RMT generates waveforms internally but they never reach the pin. CC1101's existing `setup()`, `begin_tx()`, `begin_rx()`, and `set_packet_mode()` all called `pin_mode()` on GDO0, which broke RMT-based TX/RX on the shared pin.
This PR adds a `set_gdo0_managed_externally(bool)` setter to CC1101. When set (automatically, by `ir_rf_proxy.radio_frequency`'s `to_code` when `cc1101_id` is configured), CC1101 skips all pin direction and interrupt operations on GDO0 and leaves that work to whichever component owns the pin matrix routing.
A `tx_busy_` coordination flag (with `set_tx_busy()` / `is_tx_busy()`) is also added to CC1101. The CC1101 itself doesn't touch it β it's only read by `RfProxy::on_receive()` to drop ghost RX events during a transmission.
### Validation
Configuration-time checks (`FINAL_VALIDATE_SCHEMA`):
- CC1101's `packet_mode` must be `false` (the default, async transparent mode) β packet mode buffers bytes internally and is incompatible with raw-timing transmission
- CC1101's `gdo0_pin` number must match the linked `remote_transmitter`/`remote_receiver` pin number
- Both pin configurations must set `allow_other_uses: true`
- `remote_transmitter`'s `carrier_duty_percent` must be `100%` (RF hardware handles modulation)
## Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) β [policy](https://developers.esphome.io/contributing/code/#what-constitutes-a-c-breaking-change)
- [ ] Developer breaking change (an API change that could break external components) β [policy](https://developers.esphome.io/contributing/code/#what-is-considered-public-c-api)
- [ ] Undocumented C++ API change (removal or change of undocumented public methods that lambda users may depend on) β [policy](https://developers.esphome.io/contributing/code/#c-user-expectations)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Other
**Related issue or feature (if applicable):**
- builds on the experimental `ir_rf_proxy` radio_frequency platform added in esphome/esphome#15744
**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):**
- docs updated in the `ir_rf_proxy` page on the `20260511-cc1101-rf-proxy` branch (separate esphome-docs PR to follow)
## Test Environment
- [x] ESP32 IDF (ESP32 + CC1101 board β confirmed transmission to a real RF target device; combined TX+RX scenario verified via `script/test_build_components -c ir_rf_proxy -t esp32-idf`)
## Example entry for `config.yaml`:
```yaml
spi:
clk_pin: GPIO15
mosi_pin: GPIO12
miso_pin: GPIO13
cc1101:
id: cc1101_radio
cs_pin: GPIO14
# Shared with remote_transmitter and remote_receiver β all three set allow_other_uses
gdo0_pin:
number: GPIO26
allow_other_uses: true
frequency: 433.92MHz
modulation_type: ASK/OOK
output_power: 10
remote_transmitter:
id: rf_tx
pin:
number: GPIO26
allow_other_uses: true
carrier_duty_percent: 100%
remote_receiver:
id: rf_rx
pin:
number: GPIO26
allow_other_uses: true
inverted: true
# Two entities, one CC1101, one shared GPIO pin β full half-duplex TX + RX
radio_frequency:
- platform: ir_rf_proxy
name: RF Transmitter
frequency: 433.92MHz # default carrier β overridable per-transmit via the API
remote_transmitter_id: rf_tx
cc1101_id: cc1101_radio
- platform: ir_rf_proxy
name: RF Receiver
frequency: 433.92MHz
remote_receiver_id: rf_rx
cc1101_id: cc1101_radio
```
## Checklist:
- [x] The code change is tested and works locally.
- [x] Tests have been added to verify that the new code works (under `tests/` folder).
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs).
2 Likes
i was going crazy these days following the documentation, here to ask and found your reply... so, thank you!
For those not wanting to build their own hardware interface, is there any off-the-shelf modules that can do the job?
The blogpost mentions Broadlink RM4 Pro, but that only works on 433MHz. The ESP32 world seemed unprepared for this feature release. I don't find much that doesn't require a breadboard and a solding iron except for M5Stack. A CoreS3 and Stack CC1101 modules snap together easily, cost $60 plus shipping and tax. It's expensive because it includes unnecessary features for a simple RF proxy solution.
M5Stack CC1101 Module (855-925MHz)β m5stack-store
M5Stack CoreS3 SE IoT Controller without Battery Bottomβ m5stack-store
Am I missing another supplier that has an off-the-shelf solution offering multiple frequencies? Or best to wait until the market catches up and offers such a product?
Kral84
(Kral)
May 17, 2026, 9:17am
16
@mdehaas can you post your full config pls, i have a problem
esphome:
name: jarolift-cc1101
friendly_name: jarolift-cc1101
web_server:
port: 80
esp32:
variant: ESP32C6
logger:
# Enable Home Assistant API
api:
encryption:
key: "xxxx"
ota:
- platform: esphome
password: "xxxxx0"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
spi:
clk_pin: GPIO18
mosi_pin: GPIO23
miso_pin: GPIO19
cc1101:
id: cc1101_radio
cs_pin: GPIO5
frequency: 433.92MHz
modulation_type: ASK/OOK
output_power: 10
remote_transmitter:
id: rf_tx
pin: GPIO21
carrier_duty_percent: 100
on_transmit:
then:
- cc1101.begin_tx: cc1101_radio
on_complete:
then:
- cc1101.begin_rx: cc1101_radio
remote_receiver:
id: rf_rx
pin: GPIO22
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```
I got it to work for my Novy hood, with this code (running ESPHome 2026.5)
esphome:
name: dampkap
friendly_name: dampkap
esp32:
board: esp32dev
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "..."
ota:
- platform: esphome
password: "..."
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Dampkap Fallback Hotspot"
password: "..."
captive_portal:
spi:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cc1101:
id: cc1101_radio
cs_pin: GPIO21
frequency: 433.92MHz
modulation_type: ASK/OOK
output_power: 10
symbol_rate: 5000
remote_transmitter:
id: rf_tx
pin: GPIO17
carrier_duty_percent: 100%
non_blocking: true
on_transmit:
then:
- cc1101.begin_tx: cc1101_radio
on_complete:
then:
- cc1101.begin_rx: cc1101_radio
radio_frequency:
- platform: ir_rf_proxy
id: rf_proxy
name: RF Proxy
frequency: 433.92MHz
remote_transmitter_id: rf_tx
mdehaas
(Marcel de Haas)
June 2, 2026, 8:57pm
18
Hi all,
got it working now with the following code:
#-------------------------------
# Pinout ESP32 and CC1101
#-------------------------------
# CC1101 | PIN | ESP32 | Color
# -------+-----+--------+------
# VCC | 2 | 3.3V | Red
# GND | 1 | GND | Black
# SCK | 5 | GPIO18 | Orange
# MISO | 7 | GPIO19 | Yellow
# MOSI | 6 | GPIO23 | Purple
# CSN | 4 | GPIO21 | Green
# GDO0 | 3 | GPIO4 | Blue
# GDO2 | 8 | GPIO22 | Brown
#-------------------------------
spi:
clk_pin: GPIO18
miso_pin: GPIO19
mosi_pin: GPIO23
cc1101:
id: cc1101_radio
cs_pin: GPIO21
# CC1101 GDO0 wired to one GPIO pin
remote_transmitter:
id: rf_tx
pin: GPIO4
carrier_duty_percent: 100
on_transmit:
then:
- cc1101.begin_tx: cc1101_radio
on_complete:
then:
- cc1101.begin_rx: cc1101_radio
# CC1101 GDO2 wired to a second GPIO pin
remote_receiver:
id: rf_rx
pin: GPIO22
radio_frequency:
- platform: ir_rf_proxy
name: RF Proxy Transmitter
frequency: 433.92MHz
remote_transmitter_id: rf_tx
# Optional: retune the CC1101 when an API request specifies a different
# carrier frequency. `x` is the radio_frequency call object.
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
name: RF Proxy Receiver
frequency: 433.92MHz
remote_receiver_id: rf_rx