433Mhz Wireless Doorbell monitor on an ESP

433Mhz Wireless Doorbell monitor (WD1120)

WD1120

My first attempt to connect my cheap wireless doorbell to an ESP used an analog-digital converter on an ESP32 to watch the voltage on the doorbell speaker. This worked fine, but I didn’t like the fact that my ESP had to be powered while the doorbell receiver did not, meaning I had to move the whole apparatus near a plug.

So after success with my 433Mhz transmitter attempt for my fireplace, I decided to see if I could get a 433Mhz receiver going for the doorbell directly.

An ESP can detect the signal with a very simple hardware setup - 1 pin on an ESP to a $2 433Mhz receiver.
As usual, the hard part was figuring out the codes and the timings – and even more challenging detecting the noisy signal reliably.

What you need

  • IQ America WD1120 Wireless Door Chime - $18 from Walmart. I have no idea if other doorbells will work.
  • ESP with 1 GPIO pin. An ESP-01 should do.
  • 433Mhz receiver.

I got 5 sets of xmit/receivers with antennas for $10 from Amazon.

Wiring

Wiring is dead simple

VIN is 3.3v
You can use either data pin.
Pin 6 is ground.

You don’t need to use a transmitter for this project at all (I was using it for my fireplace).
Receiver on left, transmitter on right.

Decoding the signal

My son has a Flipper Zero that can monitor 433Mhz signals, and using that it was clear the doorbell was transmitting on 433Mhz. We uploaded the raw codes from the Flipper and pulled them into Excel to take a look. It looks like each time the doorbell is pressed we get a pretty clear change in codes, with maybe some similar repeats of a sequence.
Looking at the values in those sequences, everything was fairly close to a multiple of 770us. So I took all the codes, divided by 770 and rounded to the nearest integer - that got me a nice clear signal that I could easily see repeated 10 times in the data for each button press. The code was 35 words long, with a 20.6ms delay between each sequence.

Coding it up

The remote_receiver component in ESPHome is capable of receiving the same codes as the flipper; the trick is filtering out all the stuff that isn’t of interest. First step is to use the known delay spacing. I set

idle: 20ms

to try to get each signal repeat to be processed independently. Anything smaller than 35 words I ignored. This only kind of worked; some repeats slipped through, and so I just ended up looking for pauses in the code arrays myself as well.
Then, for each signal, I compare that do my known decoded signal. Which only barely works, sometimes, and misses most of the button presses, boo.

So I need to be less stringent in the code checking. First thing I did is just check a fragment of the signal, the last 10 codes instead of all 35. Helps, but there’s still enough variability in each code length that frequently at least one of my detected values would be off by a little. So then I decided to calculate an “average error”, which is the average percentage that each code length was off its nominal value for the whole sequence. If the average per-code error was under 20%, I called it good enough, that’s a doorbell press.

That seems to be reliable enough to detect all the doorbell presses, and discriminating enough so that it’s not triggering incorrectly in the middle of the night. (Just in case, one of my automation turns off the noises late at night.)

substitutions:
  name: "esp32-doorbell"
  friendly_name: Doorbell
packages:
  esphome.bluetooth-proxy: github://esphome/firmware/bluetooth-proxy/esp32-generic.yaml@main
  esp32: !include .common-esp32.yaml
  common: !include .common.yaml

esphome:
  on_boot:
    then:
      - lambda: id(doorbell_ring).publish_state(false);

# Doorbell. 770us pulse width
# Full sequence: {-2,1,-6,1,-4,1,-7,1,-6,1,-5,1,-3,1,-4,1,-7,1,-6,1,-3,1,-4,1,-5,1,-6,1,-5,1,-4,1,-5,1,-26}; size=35

remote_receiver:
  pin: GPIO4
  #dump:
  #  - raw
  tolerance: 50%
  filter: 700us
  #  retransmit spacing is 20.6ms
  idle: 20ms
  buffer_size: 2kb # only for ESP8266
  on_raw:
    then:
      - lambda: |-
          // Use a small segment of 35-count sequence because the signal is fairly poor and imperfectly detected.
          std::vector<int> bell = {1,-6,1,-5,1,-4,1,-5,1,-26};
          int cnt = x.size();
          if (cnt < 35)
            return;
          //ESP_LOGD("doorbell", " %d codes, last %d", cnt, x[cnt-1]);
          std::vector<int> ends;
          std::string csu;
          // Raw codes seem to have repeats of the signal. Detect signal ends (pauses)
          for (int i = 0; i < cnt; i++) {
            int num = x[i];
            int uval = (int)((float)num/770.0 + ((num < 0) ? -0.5 : 0.5));
            csu += std::to_string(uval) + ",";
            if (uval < -15)
              ends.push_back(i);
          }
          if (ends.size() == 0)
            return;
          //ESP_LOGD("doorbell", "%d codes, %d signals",  x.size(), ends.size());
          //ESP_LOGD("doorbell", "%s", csu.c_str());
          int ring_counter = 0;
          // Check each signal to see if it matches the known bell pattern
          for (int j = 0; j < ends.size(); j++) {
            // Start of each signal
            int skips = ends[j] + 1 - bell.size();
            if (skips < 0)
              continue;
            // Each signal must be at least this long (i.e. distance between ends)
            if (j > 0 && ((ends[j] - ends[j-1]) < 35))
              continue;
            int mismatch = 0;
            int diff = 0;
            // Check sequence match
            for (int i = 0; i < (bell.size() - 1); i++) {
              // Close enough to nominal
              int pct = bell[i] * 770 * 100 / x[i + skips];
              diff += abs(pct - 100);
            }
            // Avg error
            diff = diff / (bell.size() - 1);
            if (diff < 20) {
              ESP_LOGD("doorbell", "ring detected at %d-%d err=%d%%", skips, ends[j], diff);
              ring_counter++;
            }
          }
          if (ring_counter > 0) {
            id(doorbell_ring).publish_state(true);
            ESP_LOGD("doorbell", "signal count %d of %d", ring_counter, ends.size());
          }
          return;

binary_sensor:
  - platform: template
    name: Ringer
    id: doorbell_ring
    publish_initial_state: true
    device_class: sound
    on_press:
      then:
        - light.turn_on: ledlight
        - homeassistant.event:
            event: esphome.button_pressed
            data:
              message: Doorbell was pressed
        - delay: 5.0s
        - binary_sensor.template.publish:
            id: doorbell_ring
            state: OFF
    on_release:
      then:
        - light.turn_off: ledlight

Entities card

1 Like