Siddhant Kumar
Project 064 · Industrial

Remote Tank Level Telemetry.

Monitors fuel and chemical tank levels from anywhere, converts level to real volume, and flags the unexpected drops that mean a leak or theft — safely, even in hazardous atmospheres.

Intermediate 12–18 hours 32 min read LevelTelemetryCloud
Jump to source Bill of materials
Remote Tank Level Telemetry — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Intermediate
Build time
12–18 hours
Indicative cost
₹5,000 – ₹9,000 (higher with IS-rated hardware)
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Industrial
Last updated
28 July 2026
Contents — 26 sections

Project Overview

Monitors fuel and chemical tank levels from anywhere, converts level to real volume, and flags the unexpected drops that mean a leak or theft — safely, even in hazardous atmospheres.

Tanks of fuel, water, chemicals or lubricant sit at remote sites — a genset's diesel tank at a tower, a farm's chemical store, a plant's bulk storage — and running one dry, or losing product to a leak or theft, is expensive and sometimes dangerous. Yet checking them means someone driving out with a dipstick. This project puts a level sensor on the tank and telemeters the reading from anywhere, so you always know how much is in each tank, get warned before it runs low (in time to reorder), and — importantly — get alerted to the unexpected drops that signal a leak or theft rather than normal use.

The measurement turns a raw level into something useful. A sensor reads the level — non-contact ultrasonic/radar from the top, or a hydrostatic pressure sensor at the bottom (pressure is proportional to the height of liquid above it) — and the firmware converts that height into a real volume using the tank's geometry or a strapping table (a lookup of level-to-volume for irregular or horizontal cylindrical tanks, where volume is very non-linear with height). So the report is "1,840 litres, 61%", not just a distance. It watches the rate of change: a slow decline matches expected consumption, a sudden step down when nothing should be drawing signals a leak or theft, and a rise means a refill (which it can log and reconcile against the delivery).

Because these tanks are remote and often in hazardous locations, the design takes both seriously: it is solar-powered and reports over LoRa or cellular (no mains, no Wi-Fi), logs locally so nothing is lost, and — critically for fuels and flammable chemicals — it is explicit that any electronics in or near a potentially explosive atmosphere must use appropriately intrinsically-safe/rated equipment, keeping sparks away from vapour. It stages alerts (low level → reorder, sudden drop → leak/theft, high → overfill) and logs history for consumption analysis and reconciliation. It is honest that hazardous-area work is governed by law and standards and that a hobby build is not automatically compliant — but as a level-to-volume, leak-and-theft-aware, remote tank monitor, it replaces the dipstick run with knowing, from anywhere, exactly how much is in every tank and when something is wrong.

Automated machinery on a factory production line
Remote tanks monitored from anywhere — real volume, reorder timing, and leak/theft alerts. Photograph sourced from Wikimedia Commons — Factory automation.jpg. Reused under the licence stated on that page; please check it before republishing.

What this project does

  • Monitors tank level remotely (non-contact or hydrostatic pressure)
  • Converts level to real volume via geometry or a strapping table
  • Warns before a tank runs low (reorder in time)
  • Flags unexpected drops (leak/theft) vs normal consumption
  • Logs refills and reconciles against deliveries
  • Runs on solar and reports over LoRa/cellular with local logging
  • Respects hazardous-area requirements for fuels/flammable chemicals

Real-World Applications

SettingHow it is used
Diesel / genset fuel tanksRemote fuel level with reorder alerts and theft/leak detection at towers, sites and farms.
Chemical / agri storageLevel and usage of stored chemicals, with overfill and leak alerts.
Water / lubricant tanksLevel telemetry for water storage and industrial fluids.
Fuel distribution / fleetsBulk-tank monitoring and delivery reconciliation across sites.

Deployment contexts where a build of this kind earns its keep.

Features & Capabilities

  • Level-to-volume conversion (strapping table for non-linear tanks)
  • Rate-of-change leak/theft detection
  • Low-level reorder and overfill alerts
  • Refill logging and delivery reconciliation
  • Solar + LoRa/cellular for remote, mains-free sites
  • Local logging through outages
  • Explicit intrinsically-safe/hazardous-area guidance

Difficulty, Time & Required Skills

AttributeValue
Difficulty levelIntermediate
Estimated completion time12–18 hours
Indicative build cost₹5,000 – ₹9,000 (higher with IS-rated hardware)
Primary disciplineIndustrial
Reference platformESP32 DevKit V1 (ESP-WROOM-32)

Skills you should have (or will pick up)

  • Non-contact and hydrostatic level measurement
  • Level-to-volume conversion and strapping tables
  • Rate-of-change leak/theft detection
  • LoRa/cellular + solar remote telemetry
  • Hazardous-area (intrinsic safety) awareness

Bill of Materials

Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.

ComponentKey specificationQtyApprox. cost
ESP32 DevKit V1 (ESP-WROOM-32)
Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC1₹450
JSN-SR04T waterproof ultrasonic sensor
The 25 cm blind zone matters — mount it above the maximum expected water level.
25–450 cm, ±1 cm, IP67 sealed transducer, 45° beam1₹450
DS18B20 waterproof temperature probe
Dozens can share one GPIO — you address them by ROM code.
−55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID1₹160
SX1278 LoRa 433 MHz module (Ra-02)
Never power the radio without an antenna — the PA will destroy itself.
−148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF121₹480
SIM800L GSM/GPRS module
Transmit bursts hit 2 A — needs a 4.0 V supply and a 1000 µF bulk capacitor.
Quad-band 850/900/1800/1900 MHz, GPRS class 12, SMS + TCP/IP1₹550
20 W 12 V polycrystalline solar panel
Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs.
Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame1₹1,200
TP4056 Li-ion charger + DW01 protection
Buy the version *with* protection ICs — the bare charger will over-discharge your cell.
1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection1₹45
18650 Li-ion cell 3400 mAh + holder
Never charge below 0 °C; always use a protected cell or a BMS.
3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge1₹450
Level sensor (non-contact or hydrostatic)
Choose intrinsically-safe/rated types for fuels/flammables
Radar/ultrasonic from top, or a submersible hydrostatic pressure transducer1₹1,500
Intrinsic-safety barrier / rated enclosure
Mandatory in hazardous zones — follow standards/law
IS barrier and rated enclosure where the atmosphere may be explosive1₹2,000
Strapping table (tank data)
Not hardware — essential for accurate volume
Manufacturer level-to-volume table for the specific tank1
Cellular modem (optional)Where no LoRa gateway exists1₹900

Estimated total: ₹8,185, excluding tools, shipping and consumables.

Tools and consumables

  • Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
  • Digital multimeter — continuity, DC volts and current ranges
  • Wire strippers, flush cutters and a small set of precision screwdrivers
  • Heat-shrink tubing and a heat gun (or a lighter, carefully)
  • A laptop with a USB port and the toolchain listed above

Hardware Specifications

PartSpecificationSupplyInterfaceReference
ESP32 DevKit V1 (ESP-WROOM-32)Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
JSN-SR04T waterproof ultrasonic sensor25–450 cm, ±1 cm, IP67 sealed transducer, 45° beam5 VTrigger/Echo or UARTDatasheet
DS18B20 waterproof temperature probe−55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID3.0–5.5 V1-Wire (multi-drop)Datasheet
SX1278 LoRa 433 MHz module (Ra-02)−148 dBm sensitivity, +20 dBm output, up to 10 km line of sight, SF7–SF123.3 VSPIDatasheet
SIM800L GSM/GPRS moduleQuad-band 850/900/1800/1900 MHz, GPRS class 12, SMS + TCP/IP3.4–4.4 V (NOT 5 V)UART AT commandsDatasheet
20 W 12 V polycrystalline solar panelVmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame12 V nominalMC4 / screw terminalsDatasheet
TP4056 Li-ion charger + DW01 protection1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection4.5–5.5 V inmicro-USB / padsDatasheet
18650 Li-ion cell 3400 mAh + holder3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge3.0–4.2 VHolder / spot-welded tabsDatasheet

Consolidated electrical and interface specifications for every active part in the build.

Power Budget & Supply Sizing

Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
JSN-SR04T waterproof ultrasonic sensor5 V30The 25 cm blind zone matters — mount it above the maximum expected water level.
DS18B20 waterproof temperature probe3.0–5.5 V1.5Dozens can share one GPIO — you address them by ROM code.
SX1278 LoRa 433 MHz module (Ra-02)3.3 V120Never power the radio without an antenna — the PA will destroy itself.
SIM800L GSM/GPRS module3.4–4.4 V (NOT 5 V)2000Transmit bursts hit 2 A — needs a 4.0 V supply and a 1000 µF bulk capacitor.
20 W 12 V polycrystalline solar panel12 V nominal1140Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs.
TP4056 Li-ion charger + DW01 protection4.5–5.5 V in1000Buy the version *with* protection ICs — the bare charger will over-discharge your cell.

Summed typical draw is 4451.5 mA. With a 1.5× design margin the supply should deliver at least 6700 mA continuously at the stated rail voltage.

Software Requirements & Development Environment

Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.

  • Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
  • Add https://espressif.github.io/arduino-esp32/package_esp32_index.json under File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager.
  • Set the correct port under Tools → Port. On Linux add yourself to the dialout group: sudo usermod -aG dialout $USER and log out and back in.
  • Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
  • Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.

Required libraries

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
OneWire + DallasTemperature 2.3.x / 3.9.xBus enumeration and conversion commands for DS18B20 probes.Library Manager → "DallasTemperature" (pulls OneWire)
Adafruit Unified Sensor 1.1.xCommon sensor event abstraction; a dependency of most Adafruit drivers.Library Manager → "Adafruit Unified Sensor"
LoRa (sandeepmistry) 0.8.0SX127x radio configuration, packet TX/RX and callbacks.Library Manager → "LoRa" by Sandeep Mistry
ArduinoJson 7.xZero-allocation JSON serialisation and parsing.Library Manager → "ArduinoJson" by Benoit Blanchon
Preferences (NVS) bundledWear-levelled key/value storage in ESP32 flash for settings.Bundled with the ESP32 core
NTPClient / configTime bundledWall-clock time from an NTP server for timestamping.Bundled (`configTime()` on ESP32)

Block Diagram

The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.

Remote Tank Level Telemetry — system block diagramFunctional block diagram of the Remote Tank Level Telemetry system. MeasureLevel sensornon-contact/hydrostaticTempcorrectionConvertESP32level → volumeStrappingtable lookupDetectRateleak/theft vs useAlertslow/overfill/dropReportDashboardvolume + historyReorderin timerightrightnone
Remote Tank Level Telemetry — system block diagram

Circuit Diagram & Wiring

Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.

Remote Tank Level Telemetry — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBLevel sensorGPIO 26/25 or ADCLevel(non-contact/hydrostatic)DS18B20GPIO 4Temp(density/sound-speedcorrection)LoRa/cellularSPI / UARTTelemetrySolar + TP40563V3 regCharged supplyIS barrierHazardous-areaprotectionStatus LEDGPIO 2Health
Remote Tank Level Telemetry — wiring schematic
PeripheralPeripheral pinController pinSignal
Level sensorTRIG/ECHO or 4-20mAGPIO 26/25 or ADCLevel (non-contact/hydrostatic)
DS18B20DQGPIO 4Temp (density/sound-speed correction)
LoRa/cellularbusSPI / UARTTelemetry
Solar + TP4056OUT3V3 regCharged supply
IS barrierin-lineHazardous-area protection
Status LEDINGPIO 2Health

Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.

Wiring explanation

  • For fuels/flammable chemicals: any sensor/electronics in or near the hazardous zone must be intrinsically-safe/rated and installed via an IS barrier per standards — keep sparks away from vapour. This is legally required.
  • Mount non-contact sensors above the max level aimed at the liquid; hydrostatic transducers sit at the tank bottom and read the pressure of liquid above.
  • Temperature-correct as needed (ultrasonic sound speed; liquid density for mass) using the DS18B20.
  • Keep the controller/battery outside the hazardous zone where possible, with only the rated sensor inside.
  • Solar-power and antenna clear of the tank structure; log locally so nothing is lost.
An HC-SR04 ultrasonic distance sensor with its two transducers
A level sensor reads the tank; the firmware converts it to volume via the strapping table. Photograph sourced from Wikimedia Commons — HC-SR04.jpg. Reused under the licence stated on that page; please check it before republishing.

System Architecture

Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.

Remote Tank Level Telemetry — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · JSN-SR04T waterproof ultrasonic sensor ·DS18B20 waterproof temperature probeDriver layerwifi · onewire · unified · lorolibApplication logicsampling loop · filtering · thresholds · state machineTransport layerLoRa/cellular → tank dashboard · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Remote Tank Level Telemetry — architecture stack

Working Principle

The point of tank telemetry is to replace the dipstick run with continuous knowledge, and its usefulness depends on turning a raw level into volume and on interpreting the change in that volume. Measuring level is the easy part: a non-contact ultrasonic/radar sensor times an echo off the surface from the top, or a hydrostatic pressure transducer at the bottom measures the pressure of the liquid column above it (pressure is directly proportional to liquid height and density). Either gives a height. But a height is not what anyone acts on — you reorder against litres remaining, reconcile a delivery in litres, and quantify a loss in litres — so the firmware must convert height to volume.

That conversion is where a subtlety lives: for many real tanks, volume is strongly non-linear with height. A horizontal cylindrical tank (the classic fuel tank) holds far more litres per centimetre near the middle than near the top or bottom, because its cross-section is a circle; a step-shaped or irregular tank is worse. So a simple "height × area" fails, and the correct approach is a strapping table — a lookup, provided by the tank manufacturer or measured, that maps level to volume for that specific tank — interpolated between entries. Using the right geometry or strapping table is the difference between a volume reading that is trustworthy and one that is comfortably wrong exactly where it matters.

The high-value intelligence is in the rate of change. A tank's volume normally declines slowly as product is consumed, matching the expected draw, and jumps up on a refill. Against that backdrop, two anomalies stand out. A sudden step down when nothing should be drawing — overnight, or when the equipment is off — is the signature of a leak or theft, and catching it fast can save a lot of product (and, for fuel, a lot of money and a safety hazard). A gradual decline faster than expected consumption can indicate a slow leak. And a rise is a delivery, which the monitor logs so it can reconcile the measured increase against the invoiced quantity — catching short deliveries. Layered on this are the operational thresholds: a low-level alert with enough lead time to reorder before running dry, and a high-level/overfill alert during filling.

Finally, the deployment realities are handled honestly, and one of them is safety-critical. Remote tanks have no mains and no Wi-Fi, so the monitor is solar-powered, reports over LoRa or cellular, and logs locally so an outage loses nothing — standard remote-telemetry design. But fuels and many chemicals create a potentially explosive atmosphere of vapour, and electronics placed in or near that zone can ignite it, so the design is emphatic that any in-zone sensor or wiring must be intrinsically safe / appropriately rated and installed through an IS barrier per the governing standards and law — keeping the electrical energy too low to ignite vapour, and keeping the battery and radio out of the hazardous zone where possible. This is not an optional nicety; it is a legal and life-safety requirement, and a hobby build is not automatically compliant. Within that frame, though, the monitor delivers exactly what tank operators need: from anywhere, the real volume in every tank, timely reordering, delivery reconciliation, and an immediate flag when a level drops for a reason that is not normal use.

The maths behind it

Hydrostatic level and volume

plainHydrostatic level and volume
Hydrostatic pressure at the bottom:
  P = ρ·g·h   →   h = P / (ρ·g)   (ρ = liquid density)

Volume from level via geometry/strapping:
  simple vertical cylinder: V = A·h
  horizontal cylinder / irregular: V = strapping_table(h)
  → volume is NON-LINEAR with h; use the table.

Leak / theft detection

plainLeak / theft detection
Expected: slow decline matching consumption, rises on refill.

  dV/dt ≈ −consumption   (normal)
  ALERT leak/theft if dV/dt << expected while no draw
     (sudden step down, or decline with equipment off)
  ALERT refill if dV/dt > 0 (log; reconcile vs delivery)

Reorder lead time

plainReorder lead time
From current volume V and average consumption rate c:
  days_to_empty ≈ V / c

  reorder when days_to_empty < lead_time + safety_margin
Gives time to reorder before running dry.

Program Flowchart

The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.

Remote Tank Level Telemetry — firmware flowchartControl flow through the main program loop. Wake on scheduleRead level; temp-correctLevel → volume (strapping)Sudden drop with no draw?Leak/theft alertCheck thresholdsLeak/theft alertCheck thresholdsLow/overfill?Reorder / overfill alertLog + reportReorder / overfill alertLog + report; sleep
Remote Tank Level Telemetry — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. Install the level sensor safely

    Fit a non-contact sensor above the max level (aimed at the liquid) or a hydrostatic transducer at the bottom. For fuels/flammables, use intrinsically-safe/rated equipment installed through an IS barrier per standards, and keep the controller/battery outside the hazardous zone where possible.

    Load the tank's strapping table (or exact geometry) for accurate volume.

  2. Set up level-to-volume and detection

    Configure the strapping-table interpolation and temperature correction, and the rate-of-change leak/theft logic and thresholds.

  3. Set up power, telemetry and logging

    Solar-power the (out-of-zone) controller, report over LoRa/cellular, and log locally so nothing is lost.

Step-by-Step Implementation Guide

Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.

  1. Convert level to volume and detect anomalies

    Read and temperature-correct the level, interpolate the strapping table to volume, and evaluate rate-of-change and threshold alerts.

    cpptank-volume.ino
    // Strapping table: level (cm) -> volume (L), interpolated.
    struct Strap { float level_cm; float volume_L; };
    Strap TABLE[] = { {0,0},{20,180},{40,520},{60,980},{80,1520},
                      {100,2080},{120,2560},{140,2900},{160,3080} };
    const int N = sizeof(TABLE)/sizeof(TABLE[0]);
    
    float levelToVolume(float level_cm) {
      if (level_cm <= TABLE[0].level_cm) return TABLE[0].volume_L;
      for (int i = 1; i < N; i++) {
        if (level_cm <= TABLE[i].level_cm) {          // linear interpolation
          float f = (level_cm - TABLE[i-1].level_cm) /
                    (TABLE[i].level_cm - TABLE[i-1].level_cm);
          return TABLE[i-1].volume_L +
                 f*(TABLE[i].volume_L - TABLE[i-1].volume_L);
        }
      }
      return TABLE[N-1].volume_L;
    }
    
    const char* anomaly(float vol, float prevVol, float dtHrs,
                        float expectedDrawLph, bool equipmentOn) {
      float dV = vol - prevVol;                        // L change
      float rate = dtHrs>0 ? dV/dtHrs : 0;             // L/h
      if (rate > 5) return "refill";                    // rise -> delivery
      if (!equipmentOn && rate < -5) return "LEAK/THEFT (drop, no draw)";
      if (rate < -1.5f*expectedDrawLph) return "faster than expected (leak?)";
      return nullptr;
    }
    float levelToVolume(float level_cm)Converts level to real volume by interpolating the tank's strapping table, correctly handling the strong non-linearity of horizontal/irregular tanks that a simple area calculation gets wrong.
    if (!equipmentOn && rate < -5) return "LEAK/THEFT (drop, no draw)"A volume drop while nothing should be drawing is the signature of a leak or theft — the high-value anomaly, distinct from normal consumption.
    if (rate > 5) return "refill"A rising volume is logged as a delivery so it can be reconciled against the invoiced quantity.
    if (rate < -1.5f*expectedDrawLph) return "faster than expected (leak?)"A decline faster than expected consumption flags a possible slow leak even without a sudden step.
  2. Alert, reorder and report

    Raise low-level (with reorder lead time), overfill, leak/theft and refill alerts, log the history for consumption analysis and reconciliation, and report over LoRa/cellular.

Complete Source Code

The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.

cpptank-level-telemetry.ino
/* ═══════════════════════════════════════════════════════════════
   Remote Tank Level Telemetry — ESP32, level→volume, leak/theft, LoRa

   Reads tank level (non-contact/hydrostatic), converts to real volume
   via a strapping table, detects leaks/theft/refills by rate-of-change,
   alerts and reorders in time, and reports over LoRa/cellular on solar.
   HAZARDOUS AREAS: use intrinsically-safe/rated equipment (law).
   ══════════════════════════════════════════════════════════════════ */

#include <OneWire.h>
#include <DallasTemperature.h>
#include <LoRa.h>
#include <SPI.h>
#include <Preferences.h>

#define PIN_TRIG 26
#define PIN_ECHO 25
#define OW_PIN    4
#define SENSOR_HEIGHT_CM 170.0f
#define REORDER_DAYS 5
#define SLEEP_S 1800            // 30 min

OneWire ow(OW_PIN); DallasTemperature temp(&ow);
Preferences prefs;

struct Strap { float lvl, vol; };
Strap TABLE[] = { {0,0},{20,180},{40,520},{60,980},{80,1520},
                  {100,2080},{120,2560},{140,2900},{160,3080} };
const int N = 9;

RTC_DATA_ATTR float prevVol = -1; RTC_DATA_ATTR uint32_t prevEpoch = 0;
RTC_DATA_ATTR float avgDrawLph = 0;

float readLevelCm(float tAir){
  float c = (331.3f + 0.606f*tAir)/10000.0f;      // cm/us
  digitalWrite(PIN_TRIG,LOW); delayMicroseconds(2);
  digitalWrite(PIN_TRIG,HIGH); delayMicroseconds(10); digitalWrite(PIN_TRIG,LOW);
  long us = pulseIn(PIN_ECHO,HIGH,30000);
  if(!us) return NAN;
  float dist = us*c/2.0f;
  return SENSOR_HEIGHT_CM - dist;                  // liquid height
}

float levelToVolume(float lvl){
  if (lvl<=TABLE[0].lvl) return 0;
  for(int i=1;i<N;i++) if(lvl<=TABLE[i].lvl){
    float f=(lvl-TABLE[i-1].lvl)/(TABLE[i].lvl-TABLE[i-1].lvl);
    return TABLE[i-1].vol + f*(TABLE[i].vol-TABLE[i-1].vol);
  }
  return TABLE[N-1].vol;
}

void transmit(float vol,float pct,const char*alert,float days){
  LoRa.beginPacket();
  LoRa.printf("{\"tank\":1,\"vol_L\":%.0f,\"pct\":%.0f,"
              "\"days\":%.1f,\"alert\":\"%s\"}",
              vol,pct,days, alert?alert:"none");
  LoRa.endPacket();
}

void setup(){
  Serial.begin(115200);
  pinMode(PIN_TRIG,OUTPUT); pinMode(PIN_ECHO,INPUT);
  temp.begin();

  temp.requestTemperatures();
  float tAir = temp.getTempCByIndex(0);
  float lvl = readLevelCm(tAir);
  float vol = levelToVolume(lvl);
  float pct = vol / TABLE[N-1].vol * 100.0f;

  uint32_t nowE = timeEpoch();
  const char *alert = nullptr;
  if (prevVol >= 0 && prevEpoch){
    float dtHrs = (nowE - prevEpoch)/3600.0f;
    float rate = dtHrs>0 ? (vol-prevVol)/dtHrs : 0;   // L/h
    if (rate > 5)                     alert = "refill";
    else if (rate < -5 && equipmentOff()) alert = "LEAK/THEFT";
    else if (rate < 0) avgDrawLph = 0.8f*avgDrawLph + 0.2f*(-rate);
  }
  prevVol = vol; prevEpoch = nowE;

  float days = avgDrawLph>0.01f ? vol/(avgDrawLph*24.0f) : 999;
  if (!alert && days < REORDER_DAYS) alert = "reorder soon";
  if (!alert && pct > 98) alert = "overfill";

  logLocal(vol,pct,alert);

  SPI.begin(); LoRa.setPins(5,14,2); LoRa.begin(433E6); LoRa.setSpreadingFactor(10);
  transmit(vol,pct,alert,days);

  esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_S*1000000ULL);
  esp_deep_sleep_start();
}
void loop(){}
float levelToVolume(float lvl)Interpolates the strapping table so the reported volume is accurate even for a tank whose volume is highly non-linear with height.
else if (rate < -5 && equipmentOff()) alert = "LEAK/THEFT"A drop while the equipment is off is flagged as leak/theft — the anomaly that distinguishes a loss from normal consumption.
else if (rate < 0) avgDrawLph = 0.8f*avgDrawLph + 0.2f*(-rate)Normal declines update the running average consumption rate, which drives the days-to-empty and reorder logic.
float days = avgDrawLph>0.01f ? vol/(avgDrawLph*24.0f) : 999Projects days-to-empty from real consumption so reorder alerts arrive with enough lead time, not when the tank is already low.
esp_sleep_enable_timer_wakeupThe monitor deep-sleeps between half-hourly reads, making a solar deployment last indefinitely at a remote site.

Configuration & Calibration

Configuration steps

  • Load the tank's strapping table (or exact geometry) and the sensor height/type.
  • Set the reorder lead time/safety margin, overfill and leak/theft thresholds, and equipment-off signal.
  • Choose LoRa/cellular telemetry, the sampling interval, and local logging.
  • For fuels/flammables, specify intrinsically-safe/rated equipment and installation per standards.

Calibration procedure

An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.

  1. Volume

    Verify the strapping-table volume against known added/removed quantities at several levels; correct the table/geometry.

  2. Anomaly thresholds

    Set leak/theft and faster-than-expected thresholds from the tank's normal consumption so real losses alarm but normal draw does not.

  3. Reorder

    Confirm days-to-empty and the reorder lead time give enough warning before running dry.

Network Architecture & Connectivity

Remote Tank Level Telemetry — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsTank monitorESP32Other tanksper-tankLoRa / cellularSite/area GWor cellularMQTT 1883Tank dashboardvolume + alertsDashboardvolume/historyPhoneleak/reorder
Remote Tank Level Telemetry — network topology

Communication protocol

Volume, %, days-to-empty and alerts report on a slow cadence; leak/theft and overfill publish immediately. Local logging covers outages.

Topic / endpointDirectionPayload
tank/1/levelnode → dashboardvolume, %, days-to-empty
tank/1/alertnode → opsleak/theft, reorder, overfill, refill
tank/1/statusnode → opsbattery, RSSI, sensor health

Message contract between the device and the broker.

Cloud platform configuration

A dashboard trends each tank's volume and consumption, projects reorder timing, reconciles refills against deliveries, and raises leak/theft alerts.

Dashboard setup

Per-tank volume/% gauges, consumption/refill history, days-to-empty, and alert log.

Mobile app integration

Immediate leak/theft and overfill alerts, and reorder-in-time notifications.

Security considerations

  • Sign readings so levels/alerts cannot be spoofed (relevant to theft).
  • Keep hazardous-area electronics intrinsically safe and out of zone where possible.
  • Alert on node silence — a disabled monitor could mask theft.

Testing Procedure & Expected Output

Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.

TestWhat you should see
Add/remove known volumesReported volume matches via the strapping table
Draw down normallySlow decline; no anomaly; consumption rate learned
Remove product with equipment offLeak/theft alert
Refill the tankRefill logged; reconcile vs delivery
Approach low levelReorder alert with adequate lead time
OverfillOverfill alert

Bench-test checklist. If a row fails, stop and fix it before moving on.

Expected output

The dashboard shows each tank's volume and %, days-to-empty, consumption/refill history, and alerts for reorder, overfill and leak/theft.

jsontank.json
{
  "tank": 1,
  "vol_L": 1840,
  "pct": 61,
  "days": 6.2,
  "alert": "none"
}

A real volume (1,840 L, 61%) with a days-to-empty projection; a sudden drop with the equipment off would instead raise a leak/theft alert, and a low projection would trigger a reorder in time.

A LoRa radio transceiver module
A LoRa/cellular link on solar power reports each tank's volume and anomalies from mains-free sites. Photograph sourced from Wikimedia Commons — LoRa module.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Volume wrong for the level

Likely cause. Wrong/simple geometry, not a strapping table

Fix. Use the tank's strapping table; interpolate correctly (volume is non-linear with height)

Missed a theft/leak

Likely cause. Thresholds too loose or no equipment-off signal

Fix. Tune rate thresholds; use the equipment-off signal to distinguish loss from draw

Reorders too late

Likely cause. Alerting only on low level

Fix. Project days-to-empty from consumption and reorder with lead time

Noisy level readings

Likely cause. Surface turbulence/foam, or temperature drift

Fix. Median-filter; temperature-correct; damp the sensor input

Safety concern (fuel)

Likely cause. Non-IS electronics in a hazardous zone

Fix. Use intrinsically-safe/rated equipment and an IS barrier per standards/law; keep electronics out of zone where possible

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

Performance Optimisation

  • Deep-sleep between reads; a slow cadence suits tanks and preserves solar budget.
  • Median-filter and temperature-correct level readings for stable volume.
  • Keep consumption/level state in RTC memory so rate/reorder logic survives sleep.
  • Report on change plus heartbeat; alerts immediately.
  • Replace every delay() with a millis() comparison — blocking delays are the single most common cause of dropped readings.
  • Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
  • Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
  • Use uint8_t / uint16_t where the range allows; on an 8-bit AVR a 32-bit add costs four times as much.
  • Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
  • Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
  • For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.

Safety Precautions

  • FOR FUELS/FLAMMABLE CHEMICALS: any device in or near the hazardous atmosphere must be intrinsically-safe/rated and installed per governing standards and law — a hobby build is not automatically compliant.
  • Keep the battery/radio out of the hazardous zone where possible; use IS barriers for in-zone sensors.
  • Handle chemicals and fuels per their safety data; avoid overfill and spillage.
  • Leak/theft alerts support, not replace, proper containment and site safety.
  • Lithium cells vent and burn when abused. Only use protected cells or a proper BMS, never charge below 0 °C, and never leave a charging pack unattended on a wooden desk.
  • Never power an RF module without its antenna fitted — the reflected power destroys the output stage. Check your local licence-free band and duty-cycle limits before transmitting.
  • Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
  • Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
  • Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.

Maintenance

  • Verify the strapping table and sensor calibration periodically.
  • Inspect IS equipment/barriers and enclosures for compliance and integrity.
  • Clean non-contact sensor faces; check hydrostatic transducers for fouling.
  • Review consumption/leak thresholds and reorder lead times.
  • Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
  • Log pack voltage. When resting voltage after a full charge drops below about 4.0 V, the cell is near end of life — replace it.
  • Wash the panel every few weeks in dusty conditions; a visible dust film costs 15–25 % of the harvest.
  • Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
  • Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
  • Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.

Future Improvements & Upgrades

A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.

  • Add temperature-compensated mass (not just volume) for fuels.
  • Add automated reordering/integration with suppliers.
  • Fuse multiple tanks and deliveries for full inventory reconciliation.
  • Add leak-rate quantification and location hints.
  • Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
  • Add over-the-air firmware updates so you never have to physically reach a deployed node again.
  • Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
  • Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
  • Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
  • Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.

Frequently Asked Questions

Why convert level to volume?

Because you act in litres, not centimetres — reorder, reconcile deliveries and quantify losses all use volume. And volume is very non-linear with height in horizontal/irregular tanks, so a strapping table is needed for accuracy.

How does it tell a leak/theft from normal use?

By rate of change and context. Normal consumption is a slow decline; a sudden drop when nothing should be drawing (equipment off, overnight) is a leak or theft, and a decline faster than expected flags a possible slow leak.

Can I put this on a diesel tank myself?

Fuel tanks create a potentially explosive atmosphere, so any in-zone electronics must be intrinsically-safe/rated and installed per standards and law. This is a legal, life-safety requirement — a hobby build is not automatically compliant.

How does it help me not run dry?

It projects days-to-empty from your real consumption and alerts with enough lead time to reorder, instead of only warning when the tank is already nearly empty.

How does it work at a remote site with no power or Wi-Fi?

Solar power, LoRa or cellular telemetry, and local logging so nothing is lost during outages — standard remote-telemetry design.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Tank level measurement methodsReference
  2. Tank strapping / calibration tablesReference
  3. Hydrostatic level measurementReference
  4. Intrinsic safety and hazardous areas (ATEX/IECEx)Reference
  5. Fuel theft/leak detectionReference