I built a simple salt level sensor for a water softener using a Wemos D1 Mini Pro and a VL53L0X time-of-flight distance sensor, fully integrated with ESPHome and Home Assistant. The sensor measures the distance to the salt surface inside the brine tank and converts it into salt height (cm) and fill level (%). In this post I’m sharing the complete parts list, wiring (I²C on D1/D2), and the working ESPHome configuration, so anyone can replicate the setup and calibrate it easily for their own tank depth.
Connections
| VL53L0X Pin | D1 Mini Pro Pin | Notes |
| VIN | 3V3 | Use 3.3V for best stability |
| GND | GNC | Common ground |
| SDA | D2 | I²C data |
| SCL | D1 | I²C clock |
Keep the I²C wires short (ideally < 20–30 cm) to avoid unstable readings.
If you must use longer wires, reduce the I²C frequency in ESPHome (e.g. frequency: 20000) and/or add 4.7k pull-ups from SDA→3V3 and SCL→3V3 (only if your breakout board doesn’t already have them).
Make sure the sensor has a clear line of sight to the salt surface (no plastic window in between, and mounted as straight as possible).
ESPHome YAML:
esphome:
name: softliq-salt-v3
friendly_name: softliq-salt-v3
esp8266:
board: d1_mini_pro
logger:
api:
encryption:
key: "YOURENCRYPTIONKEY"
ota:
- platform: esphome
password: "YOURPASSWORD"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Softliq-Salt-V3 Fallback Hotspot"
password: "YOURWIFIFALLBACKHOTSPOTPASSWORD"
captive_portal:
# I2C: D2=SDA (GPIO4), D1=SCL (GPIO5)
i2c:
sda: D2
scl: D1
scan: true
frequency: 20000
sensor:
# Abstand Sensor -> Salzoberfläche (VL53L0X liefert Meter -> cm)
- platform: vl53l0x
name: "Salztank Abstand"
id: salt_distance_cm
address: 0x29
update_interval: 180s
unit_of_measurement: "cm"
accuracy_decimals: 1
# optional: stabiler, aber etwas langsamer
# long_range: true
filters:
- multiply: 100.0
- sliding_window_moving_average:
window_size: 3
send_every: 1
- clamp:
min_value: 0.0
max_value: 60.0
# Salzhöhe in cm (0..50)
- platform: template
name: "Salztank Salzhöhe"
unit_of_measurement: "cm"
accuracy_decimals: 1
update_interval: 180s
lambda: |-
const float TANK_DEPTH_CM = 60.0;
// KALIBRIERUNG:
const float EMPTY_DISTANCE_CM = 60.0; // leer (Sensor->Boden)
const float FULL_DISTANCE_CM = 10.0; // voll (Sensor->Salz)
float d = id(salt_distance_cm).state;
if (isnan(d)) return NAN;
float height = EMPTY_DISTANCE_CM - d;
if (height < 0) height = 0;
if (height > TANK_DEPTH_CM) height = TANK_DEPTH_CM;
return height;
# Füllstand in %
- platform: template
name: "Salztank Füllstand"
unit_of_measurement: "%"
accuracy_decimals: 0
update_interval: 5s
lambda: |-
const float EMPTY_DISTANCE_CM = 60.0; // leer
const float FULL_DISTANCE_CM = 10.0; // voll
float d = id(salt_distance_cm).state;
if (isnan(d)) return NAN;
float denom = (EMPTY_DISTANCE_CM - FULL_DISTANCE_CM);
if (denom <= 0.01) return NAN;
float pct = (EMPTY_DISTANCE_CM - d) / denom * 100.0;
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
return pct;
Code-Sprache: PHP (php)
The brine tank height is 60 cm, and the “empty” reference distance is 10 cm.
Attached you’ll find the templates for your 3D printer in STL format: one set for the Wemos D1 Mini Pro enclosure (housing + lid) and one set for the sensor enclosure (housing + lid). If your sensor board doesn’t have a protective cover, I recommend gluing a transparent acrylic (plexiglass) sheet on top to protect it from salt mist and deposits.
Download
Some picture:
Home Assistant:







