Contents — 26 sections
Project Overview
Logs exactly how long a diesel genset actually runs and how much fuel it burns — so servicing is scheduled on real run-hours, not guesses, and fuel is accounted for.
A diesel generator's maintenance — oil changes, filter replacements, major services — is scheduled by run-hours, not calendar time, because an engine that ran 400 hours last month needs service far sooner than one that ran 40. Yet run-hours are usually tracked by a scribbled logbook or guessed, which means service is either done too early (wasting money) or too late (risking a costly failure), and fuel consumption — often a genset's biggest running cost and a common target of theft — is barely accounted for at all. This project automatically logs exactly when the genset runs and for how long, estimates its fuel burn, and turns that into accurate maintenance scheduling and fuel accountability.
The core is reliable run detection: the monitor senses when the generator is actually running — from its output voltage/current, its vibration, or an oil-pressure/alternator signal — and accumulates precise run-hours, logging each run's start, end and duration. From accumulated run-hours it drives maintenance: it tracks hours since the last oil change and service and alerts when each is due, so servicing happens at the right interval based on real use. And it estimates fuel burn — a genset consumes fuel roughly in proportion to its load and run-time, so run-hours (better still, run-hours weighted by load) give a fuel-consumption estimate that, checked against tank telemetry or deliveries, reveals efficiency and flags discrepancies that can mean theft.
The result is a genset that is serviced on evidence, its fuel accounted for, and its usage visible — logged and reported so an operator or fleet manager can plan maintenance, budget fuel, and spot an over-running or fuel-losing unit. It is honest that fuel burn is an estimate unless a real flow meter or tank gauge is added (run-hours alone do not measure fuel precisely, especially across varying loads), and that run detection must be robust to avoid missing or double-counting runs. But as a run-hour and fuel monitor, it replaces guesswork and logbooks with accurate, automatic records — the foundation of both proper genset maintenance and honest fuel accounting.
What this project does
- Detects when the generator is actually running (voltage/current/vibration)
- Accumulates precise run-hours and logs each run (start/end/duration)
- Tracks hours since last oil change/service and alerts when due
- Estimates fuel burn from run-hours (better with load weighting)
- Reconciles estimated fuel against tank telemetry/deliveries (theft/efficiency)
- Logs and reports usage for maintenance and fuel planning
- Replaces logbooks/guesses with automatic, accurate records
Real-World Applications
| Setting | How it is used |
|---|---|
| Backup / prime gensets | Accurate run-hour-based maintenance and fuel accounting for standby or prime power. |
| Telecom / tower sites | Remote genset run-hours and fuel monitoring across many sites. |
| Rental generator fleets | Usage-based billing/maintenance and fuel accountability. |
| Construction / events | Tracking genset usage and fuel across temporary sites. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Reliable run detection and precise run-hours
- Run-hour-based maintenance scheduling/alerts
- Fuel-burn estimation (load-weighted)
- Fuel reconciliation (theft/efficiency)
- Per-run logging
- Fleet/usage reporting
- Honest: fuel is estimated without a flow meter/gauge
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Beginner |
| Estimated completion time | 8–14 hours |
| Indicative build cost | ₹2,500 – ₹4,500 |
| Primary discipline | Energy |
| Reference platform | ESP32 DevKit V1 (ESP-WROOM-32) |
Skills you should have (or will pick up)
- Robust run detection (voltage/current/vibration)
- Run-hour accumulation and per-run logging
- Maintenance-interval tracking and alerting
- Fuel-burn estimation and reconciliation
- Usage reporting
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 |
| ZMPT101B AC voltage sensor Trim the on-board pot so the idle output sits exactly at Vcc/2. | 0–250 VAC input, 2 mA:2 mA precision transformer, ±1 % linearity | 1 | ₹220 |
| ACS712 hall-effect current sensor (20 A) Zero-offset drifts with temperature — re-zero at boot with no load. | ±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation | 1 | ₹180 |
| SW-420 vibration switch module A go/no-go switch, not a measurement — use an ADXL345 if you need amplitude or frequency. | Spring-mass normally-closed switch, adjustable sensitivity, digital out | 1 | ₹60 |
| 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 |
| 0.96″ SSD1306 OLED display Static images burn in — invert or scroll the screen periodically. | 128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C | 1 | ₹250 |
| 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 |
| microSD card 32 GB A1 class For 24/7 loggers buy a high-endurance card — normal cards die in months. | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 1 | ₹450 |
| Run-detect sensing Pick a reliable "running" signal | AC voltage/current sense, or vibration/alternator/oil-pressure signal | 1 | ₹400 |
| Fuel measurement (optional) For true fuel accounting; else estimate from run-hours | Tank level/flow meter for measured (not estimated) fuel | 1 | ₹1,200 |
| RTC + storage | Accurate time and local logging of runs | 1 | ₹250 |
| Enclosure + LoRa | Rugged housing and LoRa for remote sites | 1 | ₹700 |
Estimated total: ₹4,800, 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 |
| ZMPT101B AC voltage sensor | 0–250 VAC input, 2 mA:2 mA precision transformer, ±1 % linearity | 5 V | Analogue | Datasheet |
| ACS712 hall-effect current sensor (20 A) | ±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation | 5 V | Analogue | Datasheet |
| SW-420 vibration switch module | Spring-mass normally-closed switch, adjustable sensitivity, digital out | 3.3–5 V | Digital | 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 |
| 0.96″ SSD1306 OLED display | 128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C | 3.3–5 V | I²C (0x3C) | 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 |
| microSD card 32 GB A1 class | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 3.3 V | SDIO / SPI | 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. |
| ZMPT101B AC voltage sensor | 5 V | 20 | Trim the on-board pot so the idle output sits exactly at Vcc/2. |
| ACS712 hall-effect current sensor (20 A) | 5 V | 10 | Zero-offset drifts with temperature — re-zero at boot with no load. |
| SW-420 vibration switch module | 3.3–5 V | 15 | A go/no-go switch, not a measurement — use an ADXL345 if you need amplitude or frequency. |
| DS18B20 waterproof temperature probe | 3.0–5.5 V | 1.5 | Dozens can share one GPIO — you address them by ROM code. |
| 0.96″ SSD1306 OLED display | 3.3–5 V | 20 | Static images burn in — invert or scroll the screen periodically. |
| SX1278 LoRa 433 MHz module (Ra-02) | 3.3 V | 120 | Never power the radio without an antenna — the PA will destroy itself. |
| microSD card 32 GB A1 class | 3.3 V | 100 | For 24/7 loggers buy a high-endurance card — normal cards die in months. |
Summed typical draw is 446.5 mA. With a 1.5× design margin the supply should deliver at least 700 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 |
| PubSubClient 2.8 | Lightweight MQTT 3.1.1 client for constrained devices. | Library Manager → "PubSubClient" by Nick O'Leary |
| Adafruit SSD1306 + GFX 2.5.x | Framebuffer and text/graphics primitives for the OLED. | Library Manager → "Adafruit SSD1306" |
| LoRa (sandeepmistry) 0.8.0 | SX127x radio configuration, packet TX/RX and callbacks. | Library Manager → "LoRa" by Sandeep Mistry |
| NTPClient / configTime bundled | Wall-clock time from an NTP server for timestamping. | Bundled (`configTime()` on ESP32) |
| Preferences (NVS) bundled | Wear-levelled key/value storage in ESP32 flash for settings. | Bundled with the ESP32 core |
| SQLite 3.45+ | Zero-configuration embedded database for local logs. | Bundled with Python (`import sqlite3`) |
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 |
|---|---|---|---|
| AC voltage/current | AOUT | GPIO 34/35 | Running detection + load |
| Vibration | DOUT | GPIO 27 | Running (alt signal) |
| Fuel sensor (opt) | AOUT/PULSE | GPIO 32 | Tank level / flow |
| OLED | SDA/SCL | GPIO 21/22 | Run-hours / service due |
| LoRa | SPI | GPIO 18/19/23/5 | Remote reporting |
| RTC + SD | I²C/SPI | — | Timestamped run log |
| Supply | +/– | 3V3 reg | Power |
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
- Sense a reliable "running" signal — genset output voltage/current is unambiguous; vibration or an alternator/oil-pressure signal are alternatives.
- Sense load (current) too, so fuel burn can be load-weighted rather than a flat per-hour figure.
- Add a fuel level/flow sensor for measured fuel accounting; otherwise estimate from run-hours and reconcile against deliveries.
- Give the monitor accurate time and local logging so each run is timestamped and nothing is lost.
- Use LoRa for remote/multi-site reporting.
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 monitor exists because a diesel engine wears by use, not by time, so its maintenance must be driven by run-hours — and accurate run-hours are exactly what manual logbooks and guesses fail to provide. An engine that has run 400 hours is due for an oil change whether that took a week or three months; one that has barely run does not need service yet however long it has sat. Servicing on real run-hours means neither wasting money on premature service nor risking an expensive failure from a missed one. Everything the monitor does flows from measuring run-hours accurately and automatically.
Accurate run-hours depend on robust run detection: reliably knowing when the genset is actually running. The cleanest signal is the generator's own output — if it is producing voltage/current, it is running — but vibration, or an alternator/oil-pressure signal, can serve where output sensing is impractical. The detection must be robust in two directions: it must not miss short runs (a genset that starts, runs five minutes, and stops still accrued five minutes of wear), and it must not double-count or falsely trigger (chatter around the start/stop must be debounced). Each run is logged with its start, end and duration, and the durations accumulate into the total and the since-service counters.
From accumulated run-hours the monitor drives maintenance scheduling directly: it tracks hours since the last oil change and since the last major service against their intervals, and alerts when each is due. This is the primary payoff — service happens at the right point based on real use, planned rather than reactive, which both extends engine life and avoids the cost and downtime of failures. For a fleet, this scales to knowing which units are due when, so maintenance can be batched and planned.
The second payoff is fuel accountability, and here the design is careful about what it can and cannot claim. A diesel engine burns fuel roughly in proportion to its load and run-time, so run-hours — better still, run-hours weighted by load (measured from output current) — give a reasonable estimate of fuel consumption. That estimate is genuinely useful for budgeting and, crucially, for reconciliation: comparing estimated burn against actual fuel used (from tank telemetry or delivery records) reveals the genset's efficiency and, when the tank drops faster than the run-hours can explain, flags a leak or theft — fuel theft from remote gensets being a real and costly problem. The honesty is explicit: run-hours alone do not measure fuel precisely, especially across varying loads, so for true fuel accounting a flow meter or tank gauge is added, and the estimate is presented as such. But as a monitor that turns automatic, accurate run-hours into evidence-based maintenance scheduling and fuel accountability — replacing the logbook and the guess — it delivers exactly what keeps a genset reliably serviced and its fuel honestly accounted for.
The maths behind it
Run-hours and service due
Accumulate while running:
run_hours += Δt (while the running signal is true)
hours_since_oil = run_hours − oil_change_mark
hours_since_service = run_hours − service_mark
alert oil/service when the interval is reached.
Load-weighted fuel estimate
Diesel burn ≈ f(load) per hour:
fuel ≈ Σ ( a + b·load_fraction ) · Δt_run
(a = idle/no-load rate, b·load = load-dependent rate).
Run-hours alone (flat rate) is a rougher estimate; load
weighting improves it. Exact fuel needs a flow meter/gauge.
Fuel reconciliation (theft/efficiency)
Compare estimated burn to actual fuel used:
actual = tank_drop (telemetry) or deliveries − remaining
discrepancy = actual − estimated
large unexplained drop (actual >> estimated) → leak/theft
helps track real efficiency (L/kWh or L/hour).
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.
Set up robust run detection and load sensing
Sense a reliable running signal (genset output voltage/current preferred) with debouncing so short runs are caught and chatter is not double-counted, and sense load for fuel weighting.
Add fuel measurement or estimation
Add a fuel level/flow sensor for measured fuel, or estimate from load-weighted run-hours; set up reconciliation against tank telemetry/deliveries.
Set up maintenance tracking and reporting
Track hours since oil change/service against intervals with alerts, log each run with timestamps, and report over LoRa/dashboard.
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.
Accumulate run-hours and schedule maintenance
Detect running (debounced), accumulate run-hours and per-run logs, track since-service counters, and alert when oil/service is due.
cpprunhours.ino#define OIL_INTERVAL_H 250.0f #define SVC_INTERVAL_H 500.0f #define DEBOUNCE_MS 3000 float runHours=0, oilMark=0, svcMark=0; bool running=false; uint32_t runStart=0, stableSince=0; // Debounced run detection accumulates run-hours and logs runs. void updateRun(bool sensedRunning, uint32_t now, float dtHr){ if (sensedRunning != running){ if (!stableSince) stableSince = now; else if (now - stableSince > DEBOUNCE_MS){ // stable transition running = sensedRunning; stableSince = 0; if (running) runStart = now; else logRun(runStart, now); // record the run } } else stableSince = 0; if (running) runHours += dtHr; // accumulate while on } const char* maintenanceDue(){ if (runHours - oilMark >= OIL_INTERVAL_H) return "oil change due"; if (runHours - svcMark >= SVC_INTERVAL_H) return "service due"; return nullptr; }else if (now - stableSince > DEBOUNCE_MS){ // stable transitionRun start/stop is debounced so brief chatter does not fragment or double-count runs, while genuine short runs are still captured.if (running) runHours += dtHr; // accumulate while onRun-hours accumulate only while the genset is actually running — the accurate, use-based figure maintenance depends on.else logRun(runStart, now); // record the runEach run is logged with its start and end, giving a per-run history alongside the total.if (runHours - oilMark >= OIL_INTERVAL_H) return "oil change due"Maintenance is scheduled by hours since the last service against the interval — evidence-based, not calendar-based.Estimate fuel, reconcile and report
Estimate fuel from load-weighted run-hours (or read a fuel sensor), reconcile against tank/deliveries to flag theft/efficiency, and report usage and maintenance-due alerts.
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.
/* ═══════════════════════════════════════════════════════════════
Generator Run-Hour Monitor — ESP32
Detects genset running (debounced), accumulates precise run-hours and
per-run logs, schedules maintenance by run-hours, estimates fuel burn
(load-weighted) and reconciles against fuel data (theft/efficiency).
══════════════════════════════════════════════════════════════════ */
#include <WiFi.h>
#include <PubSubClient.h>
#include <LoRa.h>
#include <SPI.h>
#include <Preferences.h>
#define PIN_VOLT 34 // genset output voltage sense (running)
#define PIN_CURR 35 // output current (load)
#define OIL_INTERVAL_H 250.0f
#define SVC_INTERVAL_H 500.0f
#define DEBOUNCE_MS 3000
#define FUEL_NOLOAD_LPH 1.5f
#define FUEL_FULLLOAD_LPH 8.0f
Preferences prefs; WiFiClient net; PubSubClient mqtt(net);
float runHours=0, oilMark=0, svcMark=0, fuelL=0;
bool running=false; uint32_t runStart=0, stableSince=0, lastMs=0;
bool sensedRunning(){ return analogRead(PIN_VOLT) > RUN_THRESH; }
float loadFraction(){ return analogRead(PIN_CURR)/4095.0f; } // 0..1
void setup(){
Serial.begin(115200);
prefs.begin("gen",true);
runHours=prefs.getFloat("rh",0); oilMark=prefs.getFloat("oil",0);
svcMark=prefs.getFloat("svc",0); fuelL=prefs.getFloat("fuel",0);
prefs.end();
SPI.begin(); LoRa.setPins(5,14,2); LoRa.begin(433E6);
WiFi.begin(WIFI_SSID,WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
lastMs=millis();
}
void loop(){
if(!mqtt.connected()&&WiFi.status()==WL_CONNECTED) mqtt.connect("gen-1");
mqtt.loop();
uint32_t now=millis(); float dtHr=(now-lastMs)/3600000.0f; lastMs=now;
bool r = sensedRunning();
if (r != running){
if (!stableSince) stableSince=now;
else if (now-stableSince>DEBOUNCE_MS){
running=r; stableSince=0;
if (running) runStart=now;
else { logRun(runStart, now); } // record the run
}
} else stableSince=0;
if (running){
runHours += dtHr;
float lf = loadFraction();
float lph = FUEL_NOLOAD_LPH + (FUEL_FULLLOAD_LPH-FUEL_NOLOAD_LPH)*lf;
fuelL += lph * dtHr; // load-weighted estimate
}
const char *maint = nullptr;
if (runHours-oilMark >= OIL_INTERVAL_H) maint="oil change due";
else if (runHours-svcMark >= SVC_INTERVAL_H) maint="service due";
// persist and report periodically
static uint32_t last=0;
if (now-last>60000){ last=now;
prefs.begin("gen",false);
prefs.putFloat("rh",runHours); prefs.putFloat("fuel",fuelL); prefs.end();
char m[220];
snprintf(m,sizeof m,
"{\"run_h\":%.1f,\"since_oil\":%.1f,\"fuel_L\":%.1f,"
"\"running\":%s,\"maint\":\"%s\"}",
runHours, runHours-oilMark, fuelL, running?"true":"false",
maint?maint:"none");
LoRa.beginPacket(); LoRa.print(m); LoRa.endPacket();
mqtt.publish("genset/1/status", m);
if (maint) mqtt.publish("genset/1/maint", maint);
}
delay(1000);
}
Configuration & Calibration
Configuration steps
- Configure the running signal and threshold (with debounce) and the load sensing.
- Set the oil/service intervals and the maintenance-mark reset on service.
- Set fuel-rate parameters (or a fuel sensor) and reconciliation against tank/deliveries.
- Configure logging and LoRa/dashboard reporting.
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Run detection
Confirm the running signal and threshold reliably detect running (including short runs) and do not chatter; tune debounce.
Fuel estimate
Calibrate the no-load and full-load fuel rates against real consumption; add a fuel sensor for measured fuel.
Intervals
Set oil/service intervals per the engine manufacturer and confirm alerts fire at the right hours.
Network Architecture & Connectivity
Communication protocol
Run-hours, since-service, fuel and running state report periodically; maintenance-due and theft/discrepancy alerts publish on trigger. Counters persist locally.
| Topic / endpoint | Direction | Payload |
|---|---|---|
genset/1/status | node → dashboard | run-hours, since-service, fuel, running |
genset/1/maint | node → ops | oil/service due |
genset/1/fuel | node → ops | fuel estimate / reconciliation flag |
Message contract between the device and the broker.
Cloud platform configuration
A dashboard tracks each genset's run-hours and service schedule, fuel use and reconciliation, so maintenance is planned and fuel accounted for across a fleet.
Dashboard setup
Per-genset run-hours, service-due status, fuel use/estimate, per-run history, and reconciliation flags.
Mobile app integration
Maintenance-due and fuel-discrepancy (theft) alerts.
Security considerations
- Sign reports so run-hours/fuel data is trustworthy (relevant to billing/theft).
- Persist counters locally; alert on monitor silence.
- Reconcile fuel to catch 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 |
|---|---|
| Start and stop the genset | Run detected/logged with correct duration; short runs caught |
| Run under load | Load-weighted fuel estimate higher than at no load |
| Reach an oil interval | Oil-change-due alert; resets on service mark |
| Compare estimated fuel to a delivery | Reconciliation; large discrepancy flags leak/theft |
| Power-cycle the monitor | Run-hours/fuel restored from storage |
| False start signal (chatter) | Debounce prevents double-counting |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The dashboard shows total run-hours, hours since oil/service (with due alerts), estimated/measured fuel, per-run history, and reconciliation flags.
{
"run_h": 1284.5,
"since_oil": 262.0,
"fuel_L": 4120.0,
"running": true,
"maint": "oil change due"
}
With 262 hours since the last oil change (past the 250 h interval), an oil-change-due alert fires — service scheduled on real run-hours; the fuel estimate supports budgeting and, checked against deliveries, theft detection.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Sample the running signal at ~1 Hz with debounce; run-hours do not need high rate.
- Persist run-hours/fuel so resets do not lose history.
- Report periodically; maintenance/theft alerts immediately.
- Load-weight fuel for a better estimate.
- 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
- Sense genset signals safely; follow electrical/engine safety around a running genset (exhaust, hot parts, high current).
- Fuel burn from run-hours is an estimate unless a flow meter/gauge is fitted — present it as such.
- Schedule maintenance per the engine manufacturer's run-hour intervals.
- Handle fuel safely; reconciliation supports, not replaces, physical fuel security.
- Mains voltage kills. Anything on the load side of the relay is at 230 V. Do not work on a powered circuit, and never leave exposed mains wiring on a bench where someone could touch it.
- Keep at least 6 mm of creepage between the mains and low-voltage sides of any board you make, and never route mains tracks under the microcontroller.
- Have a qualified electrician do the final installation into a consumer unit or wall fitting. In most jurisdictions this is a legal requirement, not a suggestion.
- Fit an RCD/RCBO upstream and fuse the load appropriately for its rating.
- 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
- Reset the service mark at each oil change/service.
- Verify run detection and fuel calibration periodically.
- Reconcile fuel against deliveries/tank and investigate discrepancies.
- Keep the run log and reporting healthy.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Rotate the microSD card annually and keep an image of the working system. Cards used as loggers wear out silently.
- 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 a real fuel flow meter/tank gauge for measured fuel.
- Add engine parameters (temperature, oil pressure) for condition monitoring.
- Add automatic maintenance scheduling/work orders.
- Fleet-wide fuel efficiency benchmarking.
- 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.
- Generator maintenance and run-hoursReference
- Diesel engine service intervalsReference
- Genset fuel consumption vs loadReference
- Fuel theft / reconciliationReference
- Hour meters and run-time trackingReference