Remote_transmitter WORKS on d1 mini (esp8266) but FAILS on esp32

I wrote an ESPHome program to spoof one of my 433 MHz remotes and transmit using a cheap FS1000A TX board.

It works perfectly on a WEMOS D1 Mini esp8266 board.

I then tried the same code on two different esp32 boards (esp32 devkit and tdisplay) boards and it failed.

The code is the exact same except for:

  1. Appropriately changing: esp8266/board: d1_mini to esp32/board: esp32dev section
  2. Changing tx pin from D2 on the d1 mini to GPIO17 on the esp32 (I also tried a couple of other pins)

With the FS1000A TX board removed and using my oscilloscope, I see the output of the remote_transmitter signal on pin D2 of the d1 mini board BUT nothing on pin GPIO17 of the esp32 board

Looking at the logs and generously adding additional debugging logging comments, I can confirm that the code is otherwise executing exactly as expected on both boards.

I also confirmed that GPIO17 is generally working by manually generating a square wave output using digitalWrite and verifying with my scope.

So what would cause remote_transmitter to FAIL on an esp32 but SUCCEED on a more lowly esp8266 device?

Is this a known bug?

Afaik Esp32 uses RMT peripheral for that task, so there is big difference how it’s handled.

Post your yaml (for the devkit).

If you board is normal Esp32, try with GPIO18

Here is my focused YAML to demonstrate the issue:

substitutions:
  tx_pin: "18"
  remote_chan: "0"
globals:
  - id: do_remote_tx
    type: bool
    initial_value: "true"
  - id: tx_pin
    type: int
    initial_value: "${tx_pin}"
  - id: remote_chan
    type: int
    initial_value: "${remote_chan}"

esphome:
  name: test_remote
  min_version: 2025.4.0
esp32:
  board: esp32dev
  framework:
    type: arduino
ota:
  - platform: esphome
    password: !secret ota_password
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
api:
  encryption:
    key: !secret api_key
  reboot_timeout: 0s
logger:
remote_transmitter:
  pin: ${tx_pin}
  carrier_duty_percent: 100%
  rmt_channel: ${remote_chan}  # Explicit channel
interval:
  - interval: 2s
    then:
      - if:
          condition:
            lambda: 'return id(do_remote_tx);'
          then:
            - remote_transmitter.transmit_rc_switch_raw:
                  code: '1111111111111111111111111111111111111111111111111111111111111111'  # 64 pulses
                  protocol:
                    pulse_length: 1000  # 20ms high + low pulse
                    one: [1, 1]
                    sync: [0, 0]  
          else:              
            - lambda: |-
                pinMode(id(tx_pin), OUTPUT);
                for(int i=0; i < 64; i++) { // 20ms high + low pulse
                  digitalWrite(id(tx_pin), 1);
                  delayMicroseconds(1000);
                  digitalWrite(id(tx_pin), 0);
                  delayMicroseconds(1000);
                }
      - lambda: |-
          ESP_LOGW("TRANSMIT", "Pulse Transmitted to GPIO %d [do_remote_tx=%s, remote_channel=%d]" , id(tx_pin), id(do_remote_tx) ? "true" : "false", id(remote_chan));
  • If do_remote_tx is set to true (uses remote_transmitter.transmit_rc_switch_raw), then I get NO PULSES on tx_pin with my oscilloscope.
  • If set to false (uses lambda for loop with digitalWrite to generate an equivalent pulse train), then I get the expected pulse train on my scope.

In both cases, I get the expected logging message every interval (just no actual pulse in case do_remote_tx is true).
Also, I don’t get any debugger log errors.

I tried with tx_pin 18 and 17.
And with remote_chan from 0 to 7

I hope I am just missing something basic because otherwise no clue why this would fail.

you could comment out those lines.

I’m not familiar with rc_switch protocols.
For debugging try with

  - remote_transmitter.transmit_raw:
      code: [1000, -1000, 1000, -1000.....

carrier_duty_percent is a required option…
I commented out rmt_channel – still fails

I switched to:

            - remote_transmitter.transmit_raw:
                code:  [1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 
                        1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 
                        1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 
                        1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 
                        1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 
                        1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000, 
                        1000, -1000, 1000, -1000, 1000, -1000, 1000, -1000 ] # 64 pulses

AND IT WORKS ON BOTH!!!

So, seems likely to be a BUG since transmit_raw works on both esp8266 and esp32 BUT ransmit_rc_switch_raw works on esp8266 but FAILS on esp32

According to documentation is not. And with RMT it could be confusing.

Now you have defined playground.

The problems is the sync: [0, 0] line (i.e., no leading sync pattern).

This works on an esp2866 but FAILS SILENTLY on an esp32 (no compile or run-time errors or debug messages)

This is at best an inconsistency in behavior between platforms and at worst a bug.
The failure is SILENT without any error or debug messages either at compile time or run time.

Note that:
[0, 0], [0, 1], and [1,0] all FAIL.
But even a sync as short as [1, 1] SUCCEEDS

So for some reason zero sync works on an esp8266 but not an esp32.
Zero sync is a very valid use case when one just wants to generate a pulse train without a sync.

It should be easy to fix.
But at a minimum the documentation should be updated to say that zero syncs won’t work and ideally it should fail or error message either on compile or run time.
It is after all not at all obvious that [0, 0], [0,1] or [1, 0] works on some platforms but fail the entire routing silently on others.

Meanwhile, I filed this as a bug report:

I suppose either documentation is wrong or this is another bug as it fails to compile without that line (and visual studio editor also cites it as an error).

To my opinion it’s both, bug and an error in doc :wink: