LDN 00:00
012345678 minute read

Monitoring water softener salt levels with a time-of-flight sensor.

Maintenance#DIY Tech#ESPHome
Will Beeching
Softener Notification
A warm, modern farmhouse kitchen with neutral cabinetry and stone flooring. A cabinet door sits slightly open to reveal a compact white water softener unit, while soft daylight fills the space and a person walks past in motion blur.

Background

Living in the South East means we’re blessed with extremely hard water. A softener fixes that, but most rely on ion exchange and salt blocks, and remembering to check when it’s running low is a guessing game.

I didn’t want to replace a perfectly good system. I just wanted to give it a little awareness, a way for it to tell me when it needed attention.

After doing some research I had a few different options to explore:

Magnetic contact sensor (aka a door sensor).

This would involve placing a magnet on top of the salt block each time, and having the magnetic sensor trigger when it aligns with the magnet placed near an “empty” line.

Whilst this would work in theory by showing a door as closed, which I could then use to trigger a notification in Home Assistant, the salt dissolves in a way that isn’t perfectly linear, so the magnet would most likely fall off.

Load cells (aka smart scales).

Essentially, using weight sensors to calculate whether the salt had run out by measuring it when full and empty to figure out the difference. This would involve quite a lot of DIY effort, as I’d need to configure a board to work with the load cells, and there weren’t any plug and play kits that you could buy.

You could hack something together using smart scales like Withings, but it felt pretty overkill, as they’re battery operated and don’t continuously report.

Time of flight sensor (aka distance sensor).

This was the most interesting to me. A single sensor would be placed at the top of the salt housing, facing down, and calculate the distance from the sensor to the top of the salt block.

It required the least effort, and because it runs on an Arduino board, it’s simple to set up with ESPHome.

Selecting the parts

Putting this together was pretty simple. I needed:

I also bought some longer unbuckled Grove cables, as I wanted this to be continually powered and the ATOM to be far away from any water. I’ve also previously used the ATOM Lites as they work really well as Bluetooth relays for Home Assistant.

Setting it up

The actual assembly is dead simple. The sensor is made to work with ATOMs and simply plugs in with a Grove cable.

I needed to get the ATOM set up with ESPHome. You connect it via USB to your computer and, in Chrome, visit ESPHome where you select the device and choose Full install of ESPHome. As it reboots, you can add your Wi‑Fi network and get it working within a few minutes.

My Home Assistant hub is in my garage, and I wanted it to serve as the brain behind my ESPHome devices, so I installed the ESPHome Device Builder.

Home Assistant ESP
ESPHome on Home Assistant

Here I can take control of the device I just set up and add the configuration to tell it how to interpret the data coming from the time of flight sensor.

esphome_config
123456789101112131415161718192021222324252627282930313233343536373839
esphome:
  name: esphome-web-bd04f4
  friendly_name: ESPHome Web bd04f4
  min_version: 2025.9.0
  name_add_mac_suffix: false

esp32:
  variant: esp32
  framework:
    type: esp-idf

# Enable logging
logger:

# Enable Home Assistant API
api:

# Allow Over-The-Air updates
ota:
- platform: esphome

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

# Grove port on Atom Lite is I2C (GPIO26 = SDA, GPIO32 = SCL)
i2c:
  sda: 26
  scl: 32
  scan: true

# Time-of-Flight sensor
sensor:
  - platform: vl53l0x
    name: "Salt Level Distance"
    update_interval: 60s
    unit_of_measurement: "cm"
    filters:
      - multiply: 100

You then save and install, and Home Assistant should automatically detect it as a new ESPHome device to set up.

Integrated into Home Assistant
Fully integrated with readings.

Installation

Luckily, my water softener is in a cupboard which has a power socket behind it. I plugged the ATOM in with a USB‑C cable behind the cupboard, then ran the Grove cable through the very top casing of the water softener and used a sticky pad to attach it to the top of the cover.

Over time, the sensor readings rise as the salt dissolves. I learned that 30 cm means low, so I set an automation to check each morning and send a single notification if it’s above that.

Low salt notification
Low salt notification.
low_salt.yaml
123456789101112131415
alias: Alerts — Salt Low
description: Check salt level each morning at 10:30
triggers:
  - at: "10:30:00"
    trigger: time
conditions:
  - condition: numeric_state
    entity_id: sensor.salt_level_distance
    above: 30
actions:
  - action: script.notify_will_and_fran
    data:
      title: 🧊 Water Softener
      message: Salt is running low.
mode: single

Limiting it to once a day avoids a flood of alerts, and it’s interesting to see the daily regeneration patterns in the data.

Salt distance history
Salt distance history.

Wrap up

It’s been running for a few months now, and it’s surprisingly reliable, even with humidity and the occasional nudge when refilling. The softener quietly looks after itself now. I don’t think about it much, and that’s sort of the point.

There's more like this.
Keep reading.

Will Beeching
Coming soon
Lighting

Retrofitting Lutron RA2 Select for ultimate smart lighting.

Coming soon
Will Beeching
Maintenance
13 minute read

Effortless lawn care with the Mammotion Luba 2X.

Read more