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.
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
| Setting | How it is used |
|---|---|
| Diesel / genset fuel tanks | Remote fuel level with reorder alerts and theft/leak detection at towers, sites and farms. |
| Chemical / agri storage | Level and usage of stored chemicals, with overfill and leak alerts. |
| Water / lubricant tanks | Level telemetry for water storage and industrial fluids. |
| Fuel distribution / fleets | Bulk-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
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 12–18 hours |
| Indicative build cost | ₹5,000 – ₹9,000 (higher with IS-rated hardware) |
| Primary discipline | Industrial |
| Reference platform | ESP32 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.
| Component | Key specification | Qty | Approx. 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 DAC | 1 | ₹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° beam | 1 | ₹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 ID | 1 | ₹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–SF12 | 1 | ₹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/IP | 1 | ₹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 frame | 1 | ₹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 protection | 1 | ₹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 discharge | 1 | ₹450 |
| Level sensor (non-contact or hydrostatic) Choose intrinsically-safe/rated types for fuels/flammables | Radar/ultrasonic from top, or a submersible hydrostatic pressure transducer | 1 | ₹1,500 |
| Intrinsic-safety barrier / rated enclosure Mandatory in hazardous zones — follow standards/law | IS barrier and rated enclosure where the atmosphere may be explosive | 1 | ₹2,000 |
| Strapping table (tank data) Not hardware — essential for accurate volume | Manufacturer level-to-volume table for the specific tank | 1 | — |
| Cellular modem (optional) | Where no LoRa gateway exists | 1 | ₹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
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| 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 DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| JSN-SR04T waterproof ultrasonic sensor | 25–450 cm, ±1 cm, IP67 sealed transducer, 45° beam | 5 V | Trigger/Echo or UART | Datasheet |
| DS18B20 waterproof temperature probe | −55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID | 3.0–5.5 V | 1-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–SF12 | 3.3 V | SPI | Datasheet |
| SIM800L GSM/GPRS module | Quad-band 850/900/1800/1900 MHz, GPRS class 12, SMS + TCP/IP | 3.4–4.4 V (NOT 5 V) | UART AT commands | Datasheet |
| 20 W 12 V polycrystalline solar panel | Vmp 17.5 V, Imp 1.14 A, Voc 21.6 V, 350 × 290 mm, aluminium frame | 12 V nominal | MC4 / screw terminals | Datasheet |
| TP4056 Li-ion charger + DW01 protection | 1 A programmable CC/CV charge to 4.2 V ±1 %, over-discharge and short protection | 4.5–5.5 V in | micro-USB / pads | Datasheet |
| 18650 Li-ion cell 3400 mAh + holder | 3.7 V nominal, 4.2 V full, 3400 mAh, ~12.6 Wh, 2 C discharge | 3.0–4.2 V | Holder / spot-welded tabs | Datasheet |
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.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| JSN-SR04T waterproof ultrasonic sensor | 5 V | 30 | The 25 cm blind zone matters — mount it above the maximum expected water level. |
| DS18B20 waterproof temperature probe | 3.0–5.5 V | 1.5 | Dozens can share one GPIO — you address them by ROM code. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| SIM800L GSM/GPRS module | 3.4–4.4 V (NOT 5 V) | 2000 | Transmit bursts hit 2 A — needs a 4.0 V supply and a 1000 µF bulk capacitor. |
| 20 W 12 V polycrystalline solar panel | 12 V nominal | 1140 | Rated watts assume 1000 W/m² — plan for 60–70 % of nameplate in real installs. |
| TP4056 Li-ion charger + DW01 protection | 4.5–5.5 V in | 1000 | Buy 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.jsonunder 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
dialoutgroup:sudo usermod -aG dialout $USERand 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
| Library | Why it is needed | Install |
|---|---|---|
| WiFi (ESP32 core) bundled | Station/AP connection management for the ESP32. | Bundled with the ESP32 Arduino core |
| OneWire + DallasTemperature 2.3.x / 3.9.x | Bus enumeration and conversion commands for DS18B20 probes. | Library Manager → "DallasTemperature" (pulls OneWire) |
| Adafruit Unified Sensor 1.1.x | Common sensor event abstraction; a dependency of most Adafruit drivers. | Library Manager → "Adafruit Unified Sensor" |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| ArduinoJson 7.x | Zero-allocation JSON serialisation and parsing. | Library Manager → "ArduinoJson" by Benoit Blanchon |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
| NTPClient / configTime bundled | Wall-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.
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.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| Level sensor | TRIG/ECHO or 4-20mA | GPIO 26/25 or ADC | Level (non-contact/hydrostatic) |
| DS18B20 | DQ | GPIO 4 | Temp (density/sound-speed correction) |
| LoRa/cellular | bus | SPI / UART | Telemetry |
| Solar + TP4056 | OUT | 3V3 reg | Charged supply |
| IS barrier | in-line | — | Hazardous-area protection |
| Status LED | IN | GPIO 2 | Health |
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.
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.
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
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
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
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.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
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.
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.
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.
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.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.
/* ═══════════════════════════════════════════════════════════════
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(){}
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.
Volume
Verify the strapping-table volume against known added/removed quantities at several levels; correct the table/geometry.
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.
Reorder
Confirm days-to-empty and the reorder lead time give enough warning before running dry.
Network Architecture & Connectivity
Communication protocol
Volume, %, days-to-empty and alerts report on a slow cadence; leak/theft and overfill publish immediately. Local logging covers outages.
| Topic / endpoint | Direction | Payload |
|---|---|---|
tank/1/level | node → dashboard | volume, %, days-to-empty |
tank/1/alert | node → ops | leak/theft, reorder, overfill, refill |
tank/1/status | node → ops | battery, 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.
| Test | What you should see |
|---|---|
| Add/remove known volumes | Reported volume matches via the strapping table |
| Draw down normally | Slow decline; no anomaly; consumption rate learned |
| Remove product with equipment off | Leak/theft alert |
| Refill the tank | Refill logged; reconcile vs delivery |
| Approach low level | Reorder alert with adequate lead time |
| Overfill | Overfill 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.
{
"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.
Troubleshooting: Common Errors & Fixes
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 amillis()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_twhere 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
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.
- Tank level measurement methodsReference
- Tank strapping / calibration tablesReference
- Hydrostatic level measurementReference
- Intrinsic safety and hazardous areas (ATEX/IECEx)Reference
- Fuel theft/leak detectionReference