Build a Bed Occupancy Sensor with ESPHome (Under $15)


Knowing whether someone is in bed unlocks powerful automations: lights that stay off when you get up at night, climate adjustments, morning routines that wait until you actually get up. Here’s how to build one for under $15.

What You Need

PartApprox. Cost
ESP32 dev board (any)$5
2x FSR 406 (force-sensitive resistors)$4
2x 10K resistors$0.50
Dupont jumper wires$1
Total~$10-15

Where to buy:

How It Works

A force-sensitive resistor (FSR) changes its resistance based on pressure. Put one under each side of the mattress (between the mattress and the bed frame/slats), wire it into a voltage divider with a 10K resistor, and read the analog value with the ESP32’s ADC.

  • No pressure: High resistance (~1M ohm), ADC reads near 0
  • Person in bed: Low resistance (~1-50K ohm), ADC reads significantly higher

Wiring

Each FSR gets a simple voltage divider circuit:

3.3V ---[FSR]---+---[10K]--- GND
                |
             GPIO pin (ADC)
  • Left side FSR → GPIO 34 (ADC1_CH6)
  • Right side FSR → GPIO 35 (ADC1_CH7)

Use GPIO 32-39 on the ESP32 — these are the ADC-capable pins. Avoid GPIO 36 and 39 if you’re using Wi-Fi (they can have noise issues on some boards).

ESPHome Configuration

esphome:
  name: bed-sensor
  friendly_name: Bed Sensor

esp32:
  board: esp32dev

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

api:
  encryption:
    key: !secret api_key

sensor:
  - platform: adc
    pin: GPIO34
    name: "Bed Left Raw"
    id: bed_left_raw
    update_interval: 1s
    attenuation: 11db
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 5

  - platform: adc
    pin: GPIO35
    name: "Bed Right Raw"
    id: bed_right_raw
    update_interval: 1s
    attenuation: 11db
    filters:
      - sliding_window_moving_average:
          window_size: 10
          send_every: 5

binary_sensor:
  - platform: template
    name: "Bed Left Occupied"
    device_class: occupancy
    lambda: |-
      return id(bed_left_raw).state > 0.5;
    filters:
      - delayed_on: 3s
      - delayed_off: 10s

  - platform: template
    name: "Bed Right Occupied"
    device_class: occupancy
    lambda: |-
      return id(bed_right_raw).state > 0.5;
    filters:
      - delayed_on: 3s
      - delayed_off: 10s

Calibration

The threshold value (0.5 in the config above) needs calibration for your specific mattress and body weight:

  1. Flash the config and open the ESPHome logs
  2. Note the raw ADC value with nobody in bed (usually 0.01-0.1)
  3. Note the value with someone in bed (usually 0.4-2.0+)
  4. Set your threshold roughly in the middle

The delayed_on and delayed_off filters prevent false triggers from rolling over or briefly shifting weight.

Physical Installation

  1. Place each FSR flat on the bed frame slats, roughly where each person’s torso sits
  2. Run the wires down along a bed leg — DuPont connectors make this easy to disconnect
  3. The ESP32 can sit on the floor under the bed or mounted to the frame
  4. Use a USB cable long enough to reach a power outlet

No soldering required if you use DuPont jumper wires.

Home Assistant Automations

Once the binary sensors show up in HA, you can build:

  • Both in bed → trigger goodnight routine
  • One person gets up → dim hallway nightlight on (not bedroom lights)
  • Both out of bed after alarm time → start morning routine
  • Bed occupied + TV on past midnight → gentle “go to sleep” notification

Troubleshooting

Sensor always reads occupied: Your threshold is too low. Increase it. Some mattresses transfer enough weight through the slats to register even when “empty.”

Sensor never triggers: Check your wiring. The FSR needs firm, even contact with the surface. If it’s dangling or folded, it won’t register properly.

Readings are noisy: Increase the moving average window size to 20 or add a multiply filter to dampen small fluctuations.

What This Costs vs. Commercial Options

Commercial bed sensors like the Withings Sleep run $100+, require cloud accounts, and don’t integrate natively with Home Assistant. This DIY version costs $15, runs 100% locally, and gives you two independent zones.

The trade-off is setup time — about 30 minutes if you’ve used ESPHome before.

If you’re new to ESPHome, check out my ESPHome vs Tasmota comparison to understand why ESPHome is the better choice for Home Assistant. And for more sensor ideas, see my 15 devices that work without internet.

Want this config ready to go without the DIY? I sell the complete config pack with wiring diagram for $4.99.