As part of some home sensors I wanted to measure the water level in a water tank. Originally I used a PI and python to do this but this proved to not be great so I switched to use a NODE MCU dev board (ESP8266 chip) linked to an HC-SR04 sensor (HC-SR04p would be better) and have the data sent to a rest service.

The trigger and echo wires get attached to D1 and D2, the ground get attached to any GND on the board (brown wire in image) and the power gets connected to vIN (which is a 5v supply), If using the HC-SR04P then you can use the 3.3v supply (such as the one above the current ground wire)
NOTE: Strictly speaking there should be a resistor in line with the echo wire to lower it’s voltage down to 3.3v but I found that this did not seem to be needed for the NODE board (if the chip gets fried eventually then I will change this for a replacement)
The code for the chip firmware is below, this was adapted from another of my projects and C is not my main language so please bear in mind that there may be some redundant code, I may also need to review how often the chip connects to the wi-fi, this version assumes once at boot is enough.
#include "./esppl_functions.h"
#include <ESP8266WiFi.h>
#include <time.h>
#include <WiFiUdp.h>
#include <ESP8266HTTPClient.h>
#define TRIGGER 5
#define ECHO 4
char ssid[] = "mesh"; // your network SSID (name)
char pass[] = "xxxxxxxxxxxx"; // your network password
char REST[] = "http://xxxxxxxx.azurewebsites.net/api/Water/";
char URL[200];
void setup() {
Serial.begin (115200);
pinMode(TRIGGER, OUTPUT);
pinMode(ECHO, INPUT);
timesetup();
}
void loop() {
float duration, distance;
digitalWrite(TRIGGER, LOW);
delayMicroseconds(2);
digitalWrite(TRIGGER, HIGH);
delayMicroseconds(10);
digitalWrite(TRIGGER, LOW);
duration = pulseIn(ECHO, HIGH);
distance = (duration/2) / 29.1;
Serial.print("********** Ultrasonic Distance: ");
Serial.print(distance);
Serial.println(" cm");
char buffer [50];
int n=sprintf (buffer, "%f", distance);
Serial.print(buffer);
Serial.println(" : ");
int time = 0;
memset(URL, 0, sizeof URL);
strcat(URL, REST);
strcat(URL, buffer);
HTTPClient http; //Declare an object of class HTTPClient
http.begin(URL); //Specify request destination
int httpCode = http.GET(); //Send the request
if (httpCode > 0) { //Check the returning code
printf("%4d", httpCode);
String payload = http.getString(); //Get the request response payload
Serial.println(payload); //Print the response payload
}
http.end(); //Close connection
delay(60000);
printf("\n ");
}
void timesetup()
{
WiFi.mode(WIFI_STA);
wifi_station_disconnect();
wifi_set_opmode(STATION_MODE);
wifi_promiscuous_enable(false);
esppl_sniffing_enabled = false;
// We start by connecting to a WiFi network
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void timeloop()
{
}
The basic premise is that every so often the chip will take a reading and then use the REST variable with the reading appended to call a HTTP site which will log the reading (it also sets the time of the reading from it’s server time rather than needing a clock chip added to the sensor)
For info

2026 update, the original chip eventually failed so I have replaced it with a new one, but this time using ESPHOME inside home assistant since I have since started to use that for devices like this. This is the home assistant code I use now. This has some advantages for me and also deals with an issue in the old solution where if the wi-fi was rebooting while the chip was (power cuts) then I needed to reboot the chip again later since it had no handling for these issues
esphome:
name: "esp8266water2026"
friendly_name: "esp8266water2026"
esp8266:
board: esp01_1m
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "withheld"
ota:
- platform: esphome
password: "withheld"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "X Fallback Hotspot"
password: "withheld"
captive_portal:
# HTTP client
# 192 - 203mm is full.
# 200mm of water (roughly)
sensor:
- platform: ultrasonic
id: tank_distance_raw
trigger_pin: GPIO5
echo_pin: GPIO4
name: "ESP8266 Ultrasonic Distance raw"
unit_of_measurement: "mm"
accuracy_decimals: 2
update_interval: 20s
filters:
- multiply: 1000
- platform: copy
source_id: tank_distance_raw
id: tank_distance
name: "ESP8266 Ultrasonic Distance"
unit_of_measurement: "mm"
accuracy_decimals: 2
filters:
- sliding_window_moving_average:
window_size: 2
send_every: 1
- platform: template
name: "Water Tank Level"
unit_of_measurement: "%"
lambda: |-
float distance = id(tank_distance).state;
float full = 203.0; // measured full (IMPORTANT: use real value)
float empty = 400.0; // measured empty
float pct = (empty - distance) / (empty - full) * 100.0;
// Clamp between 0–100
if (pct > 120.0) return 120.0;
if (pct < 0.0) return 0.0;
return roundf(pct * 100.0) / 100.0;

