CC1101 + ESP32 (ESPHome 2026.5.1) โ€” Ceiling fan RF 433MHz: signal captured but transmit not working

Hi everyone,

I'm trying to control a 433MHz RF ceiling fan using an ESP32 + CC1101 module with ESPHome. I based my hardware wiring and initial config entirely on this excellent project by Daedilus: :backhand_index_pointing_right: GitHub - Daedilus/cc1101-esp32-esphome-fan-controller: Automate dumb ceiling fans using CC1101 RF transceiver, ESP32, and ESPHOME ยท GitHub

ESPHome version: 2026.5.1

The problem

I can receive the remote's signal perfectly โ€” the code is always the same (not rolling code), and both the RC Switch and Pronto formats look clean and consistent:

Received RCSwitch Raw: protocol=1 data='00011111000000100011000110111'

Pronto: 0000 006D 001E 0000 000D 002B 000D 002B 000D 002B 0029 000F 002A 000E 
002A 000E 002B 000E 0029 000E 000F 002A 000D 002B 000D 002B 000D 002B 000E 002A 
000D 002B 0029 000F 000D 002C 000D 002B 000D 002B 0029 000F 002A 000F 000D 002B 
000E 002A 000E 002A 002A 000F 0029 000F 000D 002B 0029 000E 0029 000F 002A 000E 000E 009B

However, when I press the button to transmit that same code, the remote_receiver only picks up this:

Pronto: 0000 006D 0001 0000 000A 009B

What I've figured out so far

After some debugging I confirmed:

The CC1101 IS transmitting RF โ€” I verified this by putting the chip into continuous TX mode for 3 seconds with cc1101.begin_tx + delay: 3000ms. During those 3 seconds the original remote stopped working, which means the CC1101 is definitely emitting at 433.92MHz and interfering.
That's a single very short pulse โ€” not the actual RF signal being transmitted.

Current ESPHome config

esphome:
  name: ventilador-rf
  friendly_name: Ventilador_RF

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    advanced:
      sram1_as_iram: true

logger:
  level: INFO

api:
  encryption:
    key: ""

ota:
  - platform: esphome
    password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Ventilador-Rf Fallback Hotspot"
    password: "XsIBKqupR2Iu"

captive_portal:

spi:
  clk_pin: GPIO14
  mosi_pin: GPIO23
  miso_pin: GPIO19
  interface: hardware

cc1101:
  id: cc1101_radio
  cs_pin: GPIO32
  frequency: 433.92MHz
  output_power: 11

remote_transmitter:
  id: rf_tx
  pin: GPIO26   # GDO0
  carrier_duty_percent: 100%
  non_blocking: False
  on_transmit:
    then:
      - cc1101.begin_tx:
          id: cc1101_radio
      - delay: 2ms
  on_complete:
    then:
      - cc1101.begin_rx:
          id: cc1101_radio

remote_receiver:
  - id: rf_rx
    pin: GPIO25   # GDO2
    dump: all
    tolerance: 25%
    filter: 250us
    idle: 4ms
    buffer_size: 10kb
    rmt_symbols: 96
    on_rc_switch:
      - logger.log:
          format: "CC1101 Received: protocol=%d, code=0x%llX"
          args: [x.protocol, x.code]

radio_frequency:
  - platform: ir_rf_proxy
    name: RF Transmitter 433MHz
    frequency: 433.92MHz
    remote_transmitter_id: rf_tx

  - platform: ir_rf_proxy
    name: RF Receiver 433MHz
    frequency: 433.92MHz
    remote_receiver_id: rf_rx

button:
  - platform: template
    name: Master Fan Off
    on_press:
      - remote_transmitter.transmit_rc_switch_raw:
          code: '00011111000000100011000110111'
          protocol: 1
          repeat:
            times: 10
            wait_time: 10ms

  - platform: template
    name: Test TX continuo
    on_press:
      - cc1101.begin_tx
      - delay: 3000ms
      - cc1101.begin_rx

Any help appreciated! :folded_hands:

1 Like

I have to disagree here. Rcswitch fits the code to any of standard protocols if tolerances allow and pronto is built for IR. You likely want to receive RAW code to debug your issue.

Thanks for the advice! I switched to dump: raw and the signal looks very clean and consistent across all captures:
This is what I get when I press once the same button as before:

[22:03:48.535][I][remote.raw:026]: Received Raw: 376, -1091, 364, -1113, 348, -1120, 1067, -392, 1083, -379, 1091, -365, 1087, -385, 1070, -387, 362, -1104, 356, -1119, 369, -1097, 345, -1115, 365, -1110, 350, -1097, 1093, -389, 352, -1093, 389, -1091, 347, -1123, 1090, -369, 1085, -395, 
[22:03:48.535][I][remote.raw:035]:   342, -1127, 351, -1093, 366, -1099, 1094, -369, 1096, -372, 383, -1097, 1094, -365, 1100, -371, 1093, -349, 387
[22:03:48.539][I][remote.raw:026]: Received Raw: 368, -1103, 364, -1091, 368, -1099, 1087, -364, 1112, -367, 1082, -370, 1100, -358, 1111, -365, 367, -1093, 366, -1097, 385, -1091, 359, -1082, 385, -1097, 359, -1098, 1084, -367, 380, -1090, 399, -1068, 381, -1089, 1090, -371, 1086, -380, 
[22:03:48.594][I][remote.raw:035]:   390, -1087, 358, -1100, 362, -1111, 1093, -365, 1086, -382, 376, -1090, 1092, -365, 1099, -360, 1093, -370, 385
[22:03:48.598][I][remote.raw:026]: Received Raw: 367, -1107, 373, -1092, 361, -1084, 1093, -371, 1107, -342, 1114, -371, 1084, -371, 1097, -371, 368, -1081, 381, -1092, 366, -1091, 367, -1098, 379, -1072, 366, -1101, 1117, -348, 363, -1091, 388, -1095, 368, -1077, 1114, -370, 1084, -372, 
[22:03:48.601][I][remote.raw:035]:   395, -1078, 360, -1095, 370, -1101, 1091, -367, 1110, -377, 357, -1093, 1091, -392, 1079, -358, 1113, -360, 371
[22:03:48.641][I][remote.raw:026]: Received Raw: 365, -1091, 391, -1077, 357, -1100, 1116, -345, 1100, -347, 1093, -398, 1069, -369, 1087, -390, 362, -1086, 392, -1078, 380, -1096, 367, -1078, 378, -1091, 367, -1104, 1073, -375, 366, -1118, 367, -1075, 358, -1099, 1116, -344, 1105, -371, 
[22:03:48.644][I][remote.raw:035]:   366, -1100, 361, -1086, 385, -1074, 1092, -371, 1089, -397, 368, -1103, 1075, -371, 1090, -371, 1085, -370, 370

Two clearly distinct pulse widths: ~370ยตs (short) and ~1090ยตs (long), which actually matches exactly the binary code RC Switch was reporting (00011111000000100011000110111), so the decoding I think it was correct all along.

I then built a button using transmit_raw with those exact averaged timings:

yaml
button:
  - platform: template
    name: Master Fan Off RAW
    on_press:
      - remote_transmitter.transmit_raw:
          carrier_frequency: 0Hz
          code: [
            370, -1090, 370, -1090, 370, -1090,
            1090, -370, 1090, -370, 1090, -370, 1090, -370, 1090, -370,
            370, -1090, 370, -1090, 370, -1090, 370, -1090, 370, -1090, 370, -1090,
            1090, -370,
            370, -1090, 370, -1090, 370, -1090,
            1090, -370, 1090, -370,
            370, -1090, 370, -1090, 370, -1090,
            1090, -370, 1090, -370,
            370, -1090,
            1090, -370, 1090, -370, 1090, -370,
            370
          ]
          repeat:
            times: 10
            wait_time: 10ms


But when I press the button I only get this in the logs, and the fan still doesn't respond:

[21:59:54.017][I][remote.raw:035]: Received Raw: 276
[22:00:00.492][W][component:522]: api took a long time for an operation (1046 ms), max is 30 ms
[22:00:00.513][I][remote.raw:035]: Received Raw: 275
[22:00:00.517][I][remote.raw:035]: Received Raw: 275

Yes, they match with RCswitch protocol 1 (350, 1050), but it's missing header.

Your signal here doesn't match the captures above.
Also, from where you got the 10ms pause and 10 repeats?