Bed Scale / Occupancy sensor

Hey!
Inspired by all the other Bed Occupancy sensor projects here, I made my own.

Summary

  • CNBTR YZC-161B 50 kg load cell, one under each leg of the bed, fitted in 3D printed holder: https://www.thingiverse.com/thing:3563099
  • The four load cells are wired as a Wheatstone bridge and connected to a hx711 load cell amplifier
  • hx711 connected (via SPI) to WeMos d1 mini (esp8266) with esphome
  • Device publish weight once per second to MQTT server running on digitalocean, together with InfluxDB and Grafana.
  • Home Assistant subscribe to the MQTT topic for weight, and perform various automations based on that (e.q. turning of light when going to bed, calculating total sleep time, pushing stats each morning via Telegram to phone).
  • Grafana instance pushes the nights sleep graph to phone each morning.

3D printed holder

Designed in FreeCAD and ordered online via Treatstock/Thingiverse/West print.


Printed to fit IKEA SULTAN bed legs.

Wiring

Can be improved :slight_smile: . The white cable is a 4 wire telephone cable. The cells are connected in a Wheatstone bridge directly, so the cells are connected to the other cells as part of the bridge, and the resulting output/input is just four wires that is connected to hx711.

Hardware

Test setup with load cells and the Wheatstone bridge (green breadboard). Also in photo: WeMos d1 mini, an DHT11 temperature and humidity sensor, the hx11 amplifier.


“Mounted” on bed table next to the bed. White cable is the connection to the bridge/load cells. Will design PCB at one point (I usually use EagleCad + OSHPARK).

esphome yaml:

esphome:
  name: bed
  platform: ESP8266
  board: d1_mini

wifi:
  ssid: -
  password: -

sensor:
  - platform: dht
    model: DHT11
    pin: D2
    temperature:
      name: "Bedroom Temperature"
    humidity:
      name: "Bedroom Humidity"
    update_interval: 60s

  - platform: hx711
    name: "Bed weight"
    dout_pin: D0
    clk_pin: D1
    gain: 128
    update_interval: 1s
    filters:
      - lambda: |-
          auto first_mass = 0.0;
          auto first_value = 21047304;
          auto second_mass = 76;
          auto second_value = 18826926;

          return map(x, first_value, second_value, first_mass, second_mass);
    unit_of_measurement: kg

mqtt:
  broker: -
  port: 8883
  username: -
  password: -
  ssl_fingerprints:
    - <>

# Enable logging
logger:

ota:

Grafana + MQTT + InfluxDB

For visualizing “sleep patterns” as well as for alerting. Once the average weight during a two minutes window goes from > 40 kg to < 40 kg, it will alert and send the graph via Telegram to phone.
Compared to a normal human scale, these load cells have a constant pressure, which make the weight to creep during night, which is something I haven’t adjusted for. But once can see and identify the deep sleep parts and movements and not focus so much on absolute weight at this point.
Also, the dht11 sensor is noisy.


Content of docker-compose.yaml running on digitalocean (togheter with a bunch of other things). The reason for running this on digitalocean and not on the raspberry pi where I run Home Assistant is because I can reuse the ssl certificates and I already have a setup with reverse proxy. I also get a more reliable setup where I will still log data even if the raspberry PI is down.

services:
  mosquitto:
    image: eclipse-mosquitto
    ports:
      - "8883:8883"
    restart: unless-stopped
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf
      - ./passwd:/mosquitto/config/passwd
      - ./cert.pem:/mosquitto/config/cert.pem
      - ./chain.pem:/mosquitto/config/chain.pem
      - ./privkey.pem:/mosquitto/config/privkey.pem

  influxdb:
    image: influxdb:1.7
    ports:
      - 8086:8086
    restart: unless-stopped
    volumes:
      - /root/data/influxdb:/var/lib/influxdb

  grafana:
    image: grafana/grafana:5.4.3
    depends_on:
      - influxdb
    ports:
      - 3000:3000
    restart: unless-stopped
    volumes:
      - /root/data/grafana:/var/lib/grafana
    environment:
      VIRTUAL_PORT: 3000
      VIRTUAL_HOST: -
      LETSENCRYPT_HOST: -
      LETSENCRYPT_EMAIL: -

  mqttbridge:
    build: ./mqttbridge
    image: nilhcem/mqttbridge
    depends_on:
      - mosquitto
      - influxdb
    restart: unless-stopped
    volumes:
      - ./bridge_config.json:/app/config.json

Sensors

Given the raw weight received every second on MQTT, I convert them into convenient sensors and binary sensors for use in automations.

sensors.yaml

# Forcing update on every new value, even if value is the same as previous value.
# Required to be able to calculate correct variance
- platform: mqtt
  name: "forced_updated_bed_weight"
  state_topic: "bed/sensor/bed_weight/state"
  force_update: true
  unit_of_measurement: "kg"
  icon: "mdi:scale"

- platform: template
  sensors:
    bed_weight_cleaned:
      friendly_name: "Bed weight cleaned"
      value_template: >-
        {% if states('sensor.forced_updated_bed_weight')|int >= 0 %}
          {{ states('sensor.forced_updated_bed_weight')|int }}
        {% else %}
          0
        {% endif %}

- platform: template
  sensors:
    persons_in_bed:
      friendly_name: "Persons in bed"
      entity_id: sensor.bed_weight_cleaned
      value_template: >-
        {% if states('sensor.bed_weight_cleaned')|int > 110 %}
          2
        {% elif states('sensor.bed_weight_cleaned')|int > 40 %}
          1
        {% else %}
          0
        {% endif %}

- platform: statistics
  name: bed_weight_stats
  entity_id: sensor.forced_updated_bed_weight
  sampling_size: 600
  precision: 6
  max_age:
    minutes: 5

binary_sensors.yaml

- platform: template
  sensors:
    in_bed:
      friendly_name: "Is in bed"
      entity_id: sensor.persons_in_bed
      value_template: >-
        {{ states('sensor.persons_in_bed')|int > 0 }}

# Sensor to determine if I have been in bed for more than one minute.
- platform: template
  sensors:
    is_steady_in_bed:
      friendly_name: "Is steady in bed"
      entity_id: binary_sensor.in_bed
      delay_on:
        minutes: 1
      delay_off:
        minutes: 1
      value_template: >-
       {{ is_state('binary_sensor.in_bed', 'on') }}

# Sensor determine if I have been laying still. This is derived from the statistics sensor
# where we determine that if the variance is less than 20/300 during the last 5 minutes (which is the max number of time bed_weight_stats_mean is keeping samples).
# This is something to tweak to identify actual deep sleep patterns.
- platform: template
  sensors:
    is_bed_weight_change_stable:
      friendly_name: "Is bed weight stable"
      entity_id: sensor.forced_updated_bed_weight
      value_template: >-
       {{ state_attr('sensor.bed_weight_stats_mean', 'variance') <= 20/300 }}

Automations

  • Turn off lights when going to bed on days before workday.
  • Track bed time, sleep time, push notification on going up.
    A bit messy to read, enjoy :slight_smile:
    automations.yaml
#
# Bed weight automations
#
- id: '1555343389279'
  alias: Turn all off when in bed on days before work
  trigger:
    platform: state
    entity_id: binary_sensor.is_steady_in_bed
    to: 'on'
  condition:
  - condition: time
    after: '19:30'
  - condition: time
    weekday:
    - sun
    - mon
    - tue
    - wed
    - thu
  action:
  - data:
      entity_id: scene.all_off
    service: scene.turn_on
  - data:
      entity_id: media_player.spotify
    service: media_player.media_pause

- id: '1555343389280'
  alias: Store time for being in bed.
  trigger:
    platform: state
    entity_id: binary_sensor.is_steady_in_bed
    to: 'on'
  action:
  - data_template:
      entity_id: input_number.being_in_bed_since
      value: '{{ as_timestamp(now()) | int }}'
    service: input_number.set_value

- id: '1555343389285'
  alias: Store time when sleeping in bed.
  trigger:
    platform: state
    entity_id: binary_sensor.is_bed_weight_change_stable
    to: 'on'
  condition:
  - condition: state
    entity_id: binary_sensor.is_steady_in_bed
    state: 'on'
  - condition: template
    value_template: "{{ states('input_number.sleeping_in_bed_since')|int == 0 }}"
  action:
  - data_template:
      entity_id: input_number.sleeping_in_bed_since
      value: '{{ as_timestamp(now()) | int }}'
    service: input_number.set_value

- id: '1555343389286'
  alias: Store Accumulated Sleeping Time (chunks)
  trigger:
    platform: state
    entity_id: binary_sensor.is_bed_weight_change_stable
    to: 'on'
  condition:
  - condition: state
    entity_id: binary_sensor.is_steady_in_bed
    state: 'on'
  action:
  - data_template:
      entity_id: input_number.accumulated_sleeping_time
      value: "{{ states('input_number.accumulated_sleeping_time')|int + 5*60 }}"
    service: input_number.set_value
  - data_template:
      entity_id: input_number.accumulated_sleeping_time_on_since
      value: '{{ as_timestamp(now()) | int }}'
    service: input_number.set_value

- id: '1555343389287'
  alias: Store Accumulated Sleeping Time (since last on when going off)
  trigger:
    platform: state
    entity_id: binary_sensor.is_bed_weight_change_stable
    to: 'off'
  condition:
  - condition: state
    entity_id: binary_sensor.is_steady_in_bed
    state: 'on'
  - condition: template
    value_template: "{{ states('input_number.accumulated_sleeping_time_on_since')|int > 0 }}"
  action:
  - data_template:
      entity_id: input_number.accumulated_sleeping_time
      value: "{{ states('input_number.accumulated_sleeping_time')|int + as_timestamp(now()) - states('input_number.accumulated_sleeping_time_on_since')|int }}"
    service: input_number.set_value
  - data:
      entity_id: input_number.accumulated_sleeping_time_on_since
      value: 0
    service: input_number.set_value

- id: '1555343389290'
  alias: Notify after disengaging the bed.
  trigger:
    platform: state
    entity_id: binary_sensor.is_steady_in_bed
    to: 'off'
  action:
  - service: notify.telegram
    data:
      title: 'Good morning!'
    data_template:
      message: "Bed time: {% set bed_since_diff = (as_timestamp(now()) - states('input_number.being_in_bed_since') | int) | int %}{% if bed_since_diff < 60 %}{{ bed_since_diff }} seconds{%- elif bed_since_diff < 3600 %}{{ (bed_since_diff/60)|round(1) }} minutes{%- else %}{{ (bed_since_diff/3600)|round(1) }} hours{%- endif %}\nSleep time: {% set val = states('input_number.sleeping_in_bed_since')|int %}{% if val > 0 %}{% set sleep_since_diff = (as_timestamp(now()) - val) | int %}{% if sleep_since_diff < 60 %}{{ sleep_since_diff }} seconds{%- elif sleep_since_diff < 3600 %}{{ (sleep_since_diff/60)|round(1) }} minutes{%- else %}{{ (sleep_since_diff/3600)|round(1) }} hours{%- endif %}.{%- else %}<no sleep time>{%- endif %}\nAccumulated Sleep time: {% set val = states('input_number.accumulated_sleeping_time')|int %}{% if val > 0 %}{% if val < 60 %}{{ val }} seconds{%- elif val < 3600 %}{{ (val/60)|round(1) }} minutes{%- else %}{{ (val/3600)|round(1) }} hours{%- endif %}.{%- else %}<no accumulated sleep time>{%- endif %}"
  - service: input_number.set_value
    data:
      entity_id: input_number.being_in_bed_since
      value: 0
  - service: input_number.set_value
    data:
      entity_id: input_number.sleeping_in_bed_since
      value: 0
  - service: input_number.set_value
    data:
      entity_id: input_number.accumulated_sleeping_time
      value: 0
  - service: input_number.set_value
    data:
      entity_id: input_number.accumulated_sleeping_time_on_since
      value: 0
19 Likes

I have had these load cells and wemos with special printed holders for over 7 months now I do really need to get stuck into this project. I kinda put it on the back burner as my mi band tracks sleep.

2 Likes

Hi guys, I am waiting for the weight sensor to arrive and I want to get ready so I can plug and play.

I am electrical noob. Currently running two temperature SHT31 sensor one directly connected to the PI and second with weemos D1.

I will prefer to connect the weignt sensor directly to the Pi. However I am opened for the second option also. Main point why I want to connect to the Pi is the report rate.

  1. I want to connect SHT31, MH-Z19b (CO2 sensor) and weight sensor on Pi simultaneously (or weemos) Is that possible?

  2. I have a carpet under the weight sensors. I don’t care about accuracy. I just want it will somehow report the weight when Iam on the bed, gf is on the bed, both are on the bed. Is that possible?

  3. Can I fix the sensors just by 3M double side tape to the bed kness? I have Ikea Malm bed.

  1. Yes, that should be possible. You can share the bus.
  2. Yes, depending on your sensor. If you are only interested in 0 persons, 1 person, 2 persons then it should not matter.
  3. It really depends on the sensor. With the sensors I have you can’t, as there need some room so the load cell can move/strain.
  • It arrived and I tested it. Firstly all to breadboard and it seemed to work fine but the values were negative and if I put some weight on to it, it risen towards zero so I calibrated it acc. to it. It worked.
  • I put it under bed. Worked quite fine for 20 minutes. Even the values were above zero, so Irecalibrated it accordingly.
  • I tried some jumps on the bed and it froze on big value. Somehow after few minutes it went back to working normal but after half an hour it stuck on same big value and that stayed all the night.
  • I have the sensors on double bed, am I damaged them or is there maybe only a contact issue?
  • Is there a way to found out which out of four is damaged/have bad contact? Any idea please?

Do you remember how much you spent on the coasters? Looking to do something similar.

By coasters do you mean the 3D printed bed leg load cell holders? I spent a total of $22.23 for all four of them when ordering via Thingiverse and picking a local 3D printing firm.

Yes & thanks

Hey have you resolved it yet or even have success?
I too have a MALM bed and am planing/thinking about a bed occupancy sensor.
Do you have the version where u can fold the whole amtress up? If not I’d recommend you put the weight sensor right beneath the slatted frame under the matress.
I unfortunately have the afore mention version so I could only potentially place the weights sensors under the whole bed which weights around 100kg already so I’d be very quick to overload them.

 - platform: hx711
    name: "Bed weight"
    dout_pin: D0
    clk_pin: D1
    gain: 128
    update_interval: 1s
    filters:
      - lambda: |-
          auto first_mass = 0.0;
          auto first_value = 21047304;
          auto second_mass = 76;
          auto second_value = 18826926;

          return map(x, first_value, second_value, first_mass, second_mass);
    unit_of_measurement: kg

Can you talk a bit about the filter you are using here? Like what exactly it is doing?

I’ve just set this up on my son’s bed. I’m just using the “calibrate_linear” filter and it works OK but wondering if there is a better method.

2 Likes

The calibrate_linear is just a shorthand for the mapping function I’m using. The calibrate_linear function did not exist when I wrote that code, but they do the same.

@johboh just finished setting this up again after moving/getting a new bed. Switched over to using 4 load sensors spread out further vs 2. Last night was the first time using it and I am having an issue where the value doesn’t stay consistent/goes negative over time.

This is the graph of my scale from the last night.

Have you seen similar issues? If so how did you handle it? If not, and ideas why I might be having this issue?

Hey!
Does it show the correct weight if when you just sit in the bed, stand up and sit again? I.e. is it just over a longer period of time it drift?
I had a similar issue where the weight was varying a lot during the night, but then I realized that I had a fifth leg in the center under the bed. I adjusted the leg so it does not touch the floor and now the weight is quite stable during the night.

Hi!

Looks cool, working on something similar based on your design. I was wondering where you got the parts that sit on the loadcells? Did they come with the sensors or did you have those printed too?

The small black “spacers” came attached like that with the cells (and are screwed on). But they are not really needed, but I have designed the holders after them. Other cells I have seen does not have them, but still a good center point upon you can put the load.

Were you ever able to solve this? I’ve exchanged my HX711 with another, but still a lot of drifting values…unable to use it in this way.

Actually I just swapped mine out with a new one last night. The first night I never went negative so that’s a plus. I’ll give it another few days and see if I can detect a useable pattern.

What type of board did you replace it with?

Similar HX711, but that was drifting as well…removed the bed sensor completely :frowning:

If there is drift I suspect that the load cells or the support is deforming during continuous load for the period of the night. I saw similar behavior when I had soft distances between cells and the floor. I’m till using the same setup as described in the first post, and have zero drifting during night:

So mine is doing waaaay better with the spark fun board and not the cheap aliexpress board.

Still seems to fluctuate a bit, but that could be because of my setup.

@pingme maybe try one of the spark fun boards, not cheap $18 with shipping I think for me.