Siddhant Kumar
Project 076 · Energy

Generator Run-Hour Monitor.

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.

Beginner 8–14 hours 30 min read GensetLogsCloud
Jump to source Bill of materials
Generator Run-Hour Monitor — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Beginner
Build time
8–14 hours
Indicative cost
₹2,500 – ₹4,500
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Energy
Last updated
28 July 2026
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.

Automated machinery on a factory production line
A genset serviced on real run-hours, not guesses — and its fuel accounted for. 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

  • 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

SettingHow it is used
Backup / prime gensetsAccurate run-hour-based maintenance and fuel accounting for standby or prime power.
Telecom / tower sitesRemote genset run-hours and fuel monitoring across many sites.
Rental generator fleetsUsage-based billing/maintenance and fuel accountability.
Construction / eventsTracking 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

AttributeValue
Difficulty levelBeginner
Estimated completion time8–14 hours
Indicative build cost₹2,500 – ₹4,500
Primary disciplineEnergy
Reference platformESP32 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.

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
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 % linearity1₹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 isolation1₹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 out1₹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 ID1₹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²C1₹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–SF121₹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 recommended1₹450
Run-detect sensing
Pick a reliable "running" signal
AC voltage/current sense, or vibration/alternator/oil-pressure signal1₹400
Fuel measurement (optional)
For true fuel accounting; else estimate from run-hours
Tank level/flow meter for measured (not estimated) fuel1₹1,200
RTC + storageAccurate time and local logging of runs1₹250
Enclosure + LoRaRugged housing and LoRa for remote sites1₹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

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
ZMPT101B AC voltage sensor0–250 VAC input, 2 mA:2 mA precision transformer, ±1 % linearity5 VAnalogueDatasheet
ACS712 hall-effect current sensor (20 A)±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation5 VAnalogueDatasheet
SW-420 vibration switch moduleSpring-mass normally-closed switch, adjustable sensitivity, digital out3.3–5 VDigitalDatasheet
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
0.96″ SSD1306 OLED display128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C3.3–5 VI²C (0x3C)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
microSD card 32 GB A1 classA1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended3.3 VSDIO / SPIDatasheet

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.
ZMPT101B AC voltage sensor5 V20Trim the on-board pot so the idle output sits exactly at Vcc/2.
ACS712 hall-effect current sensor (20 A)5 V10Zero-offset drifts with temperature — re-zero at boot with no load.
SW-420 vibration switch module3.3–5 V15A go/no-go switch, not a measurement — use an ADXL345 if you need amplitude or frequency.
DS18B20 waterproof temperature probe3.0–5.5 V1.5Dozens can share one GPIO — you address them by ROM code.
0.96″ SSD1306 OLED display3.3–5 V20Static images burn in — invert or scroll the screen periodically.
SX1278 LoRa 433 MHz module (Ra-02)3.3 V120Never power the radio without an antenna — the PA will destroy itself.
microSD card 32 GB A1 class3.3 V100For 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.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
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
Adafruit SSD1306 + GFX 2.5.xFramebuffer and text/graphics primitives for the OLED.Library Manager → "Adafruit SSD1306"
LoRa (sandeepmistry) 0.8.0SX127x radio configuration, packet TX/RX and callbacks.Library Manager → "LoRa" by Sandeep Mistry
NTPClient / configTime bundledWall-clock time from an NTP server for timestamping.Bundled (`configTime()` on ESP32)
Preferences (NVS) bundledWear-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.

Generator Run-Hour Monitor — system block diagramFunctional block diagram of the Generator Run-Hour Monitor system. DetectRunning?V/I/vibrationLoadfor fuel weightingFuel (opt)level/flowAccumulateESP32run-hoursServicesince lastEstimateFuel burnload-weightedReconcilevs deliveriesReportMaintenancedue alertsUsage/fuellogrightrightnone
Generator Run-Hour Monitor — 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.

Generator Run-Hour Monitor — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBAC voltage/currentGPIO 34/35Running detection +loadVibrationGPIO 27Running (alt signal)Fuel sensor (opt)GPIO 32Tank level / flowOLEDGPIO 21/22Run-hours / servicedueLoRaGPIO 18/19/23/5Remote reportingRTC + SDTimestamped run logSupply3V3 regPower
Generator Run-Hour Monitor — wiring schematic
PeripheralPeripheral pinController pinSignal
AC voltage/currentAOUTGPIO 34/35Running detection + load
VibrationDOUTGPIO 27Running (alt signal)
Fuel sensor (opt)AOUT/PULSEGPIO 32Tank level / flow
OLEDSDA/SCLGPIO 21/22Run-hours / service due
LoRaSPIGPIO 18/19/23/5Remote reporting
RTC + SDI²C/SPITimestamped run log
Supply+/–3V3 regPower

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.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
ESP32 module detecting running (debounced) and accumulating precise run-hours and fuel. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.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.

Generator Run-Hour Monitor — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · ZMPT101B AC voltage sensor · ACS712hall-effect current sensor (20 A) · DS18B20 waterproof temperature probeDriver layerwifi · pubsub · ssd1306 · lorolibApplication logicsampling loop · filtering · thresholds · state machineTransport layerLoRa → fleet maintenance/fuel dashboard · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Generator Run-Hour Monitor — architecture stack

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

plainRun-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

plainLoad-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)

plainFuel 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.

Generator Run-Hour Monitor — firmware flowchartControl flow through the main program loop. Sense running signalRunning?Accumulate run-hours + loadIdle (log run end)Accumulate run-hours + loadIdle (log run end)Service interval reached?Maintenance-due alertEstimate fuel; reconcileMaintenance-due alertEstimate fuel; reconcileReport; repeat
Generator Run-Hour Monitor — 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. 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.

  2. 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.

  3. 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.

  1. 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.
  2. 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.

cppgenerator-runhour-monitor.ino
/* ═══════════════════════════════════════════════════════════════
   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);
}
bool r = sensedRunning();The running state is sensed from the genset's output and debounced over several seconds, so runs are counted accurately without chatter.
if (running){ runHours += dtHr;Run-hours accumulate only while running — the use-based figure that maintenance scheduling and fuel estimation both need.
float lph = FUEL_NOLOAD_LPH + (FUEL_FULLLOAD_LPH-FUEL_NOLOAD_LPH)*lfFuel burn is estimated as a load-weighted rate — better than a flat per-hour figure since a loaded genset burns more.
if (runHours-oilMark >= OIL_INTERVAL_H) maint="oil change due"Maintenance is due by hours since the last service — evidence-based scheduling that replaces the logbook guess.
prefs.putFloat("rh",runHours)Run-hours and fuel are persisted so a power blip or reset never loses the genset's accumulated history.

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.

  1. Run detection

    Confirm the running signal and threshold reliably detect running (including short runs) and do not chatter; tune debounce.

  2. Fuel estimate

    Calibrate the no-load and full-load fuel rates against real consumption; add a fuel sensor for measured fuel.

  3. Intervals

    Set oil/service intervals per the engine manufacturer and confirm alerts fire at the right hours.

Network Architecture & Connectivity

Generator Run-Hour Monitor — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsGenset monitorESP32Other gensetsfleetLoRa / Wi-FiSite gatewayto serverMQTTMaintenance/fuelrun-hours + fuelDashboardusage/serviceOpsmaint/theft alerts
Generator Run-Hour Monitor — network topology

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 / endpointDirectionPayload
genset/1/statusnode → dashboardrun-hours, since-service, fuel, running
genset/1/maintnode → opsoil/service due
genset/1/fuelnode → opsfuel 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.

TestWhat you should see
Start and stop the gensetRun detected/logged with correct duration; short runs caught
Run under loadLoad-weighted fuel estimate higher than at no load
Reach an oil intervalOil-change-due alert; resets on service mark
Compare estimated fuel to a deliveryReconciliation; large discrepancy flags leak/theft
Power-cycle the monitorRun-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.

jsongenset.json
{
  "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.

A Grafana time-series dashboard
A dashboard schedules maintenance by run-hours and reconciles fuel to catch theft. Photograph sourced from Wikimedia Commons — Grafana dashboard.png. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Run-hours inaccurate

Likely cause. Poor run detection / missed short runs

Fix. Use a reliable running signal; tune threshold/debounce to catch short runs without chatter

Fuel estimate far off

Likely cause. Flat rate / no load weighting

Fix. Load-weight the estimate; calibrate rates; add a fuel sensor for measured fuel

Service alerts wrong

Likely cause. Intervals/marks not set

Fix. Set manufacturer intervals; reset the mark on each service

Theft not caught

Likely cause. No reconciliation

Fix. Compare estimated burn to tank telemetry/deliveries; flag large discrepancies

History lost on reset

Likely cause. Not persisting counters

Fix. Persist run-hours/fuel to non-volatile storage

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.

An I²C device is not detected

Likely cause. Wrong address, missing pull-ups, swapped SDA/SCL, or a bus too long for the pull-up value.

Fix. Run an I²C scanner sketch first — it should print the device address. Most breakout boards include 4.7 kΩ pull-ups, but if you have chained four of them the parallel resistance is too low; remove the pull-ups from all but one board. Keep the bus under 30 cm at 100 kHz.

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

  • 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 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

  • 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

Why schedule maintenance by run-hours?

Because an engine wears by use, not calendar time. A genset that ran 400 hours needs service far sooner than one that ran 40, so servicing on real run-hours avoids both premature service and missed-service failures.

How does it know when the genset is running?

By sensing a reliable running signal — usually the generator's own output voltage/current, or vibration/alternator/oil-pressure — debounced so short runs are caught and chatter is not double-counted.

Is the fuel figure accurate?

It is an estimate from load-weighted run-hours unless you add a real flow meter or tank gauge. It is good for budgeting and, reconciled against deliveries, for spotting theft — but run-hours alone do not measure fuel exactly.

How does it help catch fuel theft?

By reconciling estimated burn against actual fuel used (tank telemetry or deliveries). When the tank drops much faster than the run-hours can explain, it flags a possible leak or theft — a real, costly problem at remote gensets.

What does it replace?

The scribbled logbook and the guess. It gives automatic, accurate run-hours and per-run history — the foundation of proper genset maintenance and honest fuel accounting.

References & Learning Resources

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

  1. Generator maintenance and run-hoursReference
  2. Diesel engine service intervalsReference
  3. Genset fuel consumption vs loadReference
  4. Fuel theft / reconciliationReference
  5. Hour meters and run-time trackingReference