Smart bed warmer with ESP-01

Smartify a dumb bedwarmer

I have a Perfect Fit SoftHeat heated mattress pad with dumb controls that I wanted to make smarter, mainly so I could turn it on with a timer. This model has a pair of controllers, one for each side of the bed, with a rotary dial for the heat setting and a on/off toggle button.

(with my addition)

You can’t just plug this thing into a smart plug, because the controller always starts up Off when first plugged in. You actually need to toggle the button to get it to turn on, ever.

Parts List

  • Perfect Fit SoftHeat heated mattress pad. There are some bad reviews there; do your due diligence. I bought mine in 2013, it lasted 5 years before one side stopped working. The company sent me a replacement, for free, and let me keep my old one. New one is working fine.
  • ESP-01S. Like a buck from AliExpress
  • ESP-01S Relay Board. These are cool, ESP-01S just plugs right in. Mine cost $1.55 from AliExpress.
  • If you have a 3D printer, you can print a little case like mine.

Power

The relay boards have a power regulator on them that steps down to the 5v required for the relay itself, and to 3.3v for the ESP01. I looked up the part, and it’s rated up to 15v input. The output voltage of my bedwarmer power supply is 16v; I called this close enough and just plugged it right in. If you’re more concerned about it, you should regulate it down with some additional parts.

Wiring

Wiring this up is easy, but not quite trivial. GPIO0 runs the relay. But we need one other pin to use as an input to see if the heater output is on or off. I used GPIO3, and bent the pin out so that it did not plug into the yellow riser on the relay board. I connected this pin to a 1Mohm resistor, and from there to the positive side of the wires going to the mattress pad.
There’s a total of 5 wires you need to attach to the controller:

  1. Power supply in (+16v) to the relay board power connector (+). I just soldered my wire onto where the wires came in from the power supply.
  2. Power supply ground to the relay board power connector gnd.
  3. Mattress pad power out (+16v, pwm’ed at a slow rate to adjust output temperature) to the 1Mohm resistor above. The resistor prevents the relatively high voltage from over-saturating the input pin on the esp. Attached at the wires going out the mattress pad. (There are 4 total wires connecting the controller; the grounds are common.)
  4. SW1: Run a wire from one side of the switch to the relay common (center pin).
  5. SW2: Run a wire from the other side of the switch to the normally open (NO) pin.

Powering the relay will then close the circuit, acting like a button push. The GPIO3 input will let us determine if the controller is currently heating the pad or not.

Code

There’s a few failsafes I wanted to build in, since it is a heater after all. First, if we get disconnected from Home Assistant for some reason, I don’t want to leave the heater on. So:

api:
  # failsafe
  on_client_disconnected:
    - switch.turn_off: heater

Second, when we’re first booting, make sure that the relay is the “off” position, and, again, make sure the heater is off.

# Make sure relay isn't stuck on, and heater is off at boot
esphome:
  on_boot:
    - priority: 300
      then:
        # a script runs asynchronously
        - script.execute: force_off
        - number.set: 
            id: brightness
            value: 10

script:
  - id: force_off
    then:
      - switch.turn_off: relay
      - delay: 300ms
      - switch.turn_off: heater

And then the meat: two switches and a sensor. The first switch is hidden from Home Assistant (internal: true), this toggles the relay momentarily. When we turn on this switch, it connects the relay, and then the switch turns itself off after 300ms, releasing the button. So this looks like a short press and release of the button to the bedwarmer controller. A toggle, but it is blind to whether we toggled on or off. That’s what the second switch is for: it checks the state of the heating element, and if it’s powered, it checks the “is heating” sensor, and acts appropriately: if heating, and you want to turn it off, it toggles. If not heating, and you want to turn it on, it toggles. But if it’s already in the state you want, it doesn’t do anything.

switch:
  # Relay shield board
  - platform: gpio
    pin: GPIO0
    name: relay
    id: relay
    inverted: True
    internal: True
    restore_mode: ALWAYS_OFF
    # Momentary push toggle button at "turn on"
    on_turn_on:
      - delay: 300ms
      - switch.turn_off: relay
        # rate limit
      - delay: 1s

  # Heater on/off switch
  - platform: template
    name: heater
    id: heater
    device_class: switch
    icon: mdi:bed
    restore_mode: ALWAYS_OFF
    lambda: |-
      return (id(heating).state);
    turn_on_action:
      - if:
          condition:
            binary_sensor.is_off: heating
          then:
            - switch.turn_on: relay
    turn_off_action:
      - if:
          condition:
            binary_sensor.is_on: heating
          then:
            - switch.turn_on: relay

For the sensor, we have a 4 second delayed_off filter. This is because the way the heater works is by turning full on and full off intermittently, with a frequency depending on the warmness dial setting. The longest interval between on and off is 3 seconds, so adding this filter prevents the sensor from just flipping on and off every 3 seconds. Oh, and it turns on the LED when it senses heating, for a visual indicator that the sensor is working.

# Detect heating on gpio3
binary_sensor:
  - platform: gpio
    name: heating
    id: heating
    device_class: heat
    internal: true
    pin:
      number: GPIO3
      mode:
        input: True
        pullup: False
      inverted: True
    filters:
      # heating is pwm'ed at a low rate
      - delayed_off: 4s
    on_press:
      then:
        - light.turn_on: ledlight
    on_release:
      then:
        - light.turn_off: ledlight

My ledlight is much more complicated than most people care about, but for completeness I’ll include it here. You can adjust the brightness; full brightness was keeping me up.

globals:
  - id: blueled_brightness
    type: int
    restore_value: no
    initial_value: '90'

output:
  - id: blueled
    platform: esp8266_pwm
    pin: GPIO02
    frequency: 200 Hz
    inverted: true

light:
  - platform: status_led
    name: "led"
    id: ledlight
    icon: mdi:led-variant-on
    output: blueled
    restore_mode: ALWAYS_OFF
    on_turn_on:
      # apparently lambda races turn on, so delay
      - delay: 10ms
      - lambda: id(blueled).set_level(id(blueled_brightness)/100.0);

Automations

Ok, great, now you’ve got a wifi bed heater, what do you do with it?

  1. Set up an automation that automatically turns it off 30 minutes after it is turned on. No more waking up overheated.
  2. Set up an automation to automatically turn it on at 10:00pm, if the temperature is below 45 degrees and if you’re home (e.g. phone connected to wifi). Now your bed is warm and toasty before getting in.

That’s it. All I wanted.

1 Like