Work · 001 · Hardware + Software

eFuelPro

A fuel station that could not tell you what it had sold until the end of the day, and now tells you every time a nozzle goes down.

01 The problem

Cash, paper, and a number nobody could check.

A fuel forecourt runs on trust and a clipboard. The dispenser knows exactly what it dispensed; the owner finds out later, from a total that has been through several hands. There is no dispute to resolve because there is no record to dispute.

The ask was simple to state: print a receipt for every sale, keep a ledger the owner can read from anywhere, and make loyalty work without a card reader that people forget to use.

The dispenser had a serial port and no documentation for it. Everything else followed from that.

02 Decoding the bus

A logic analyser on the RS485 pair, and a structured matrix of controlled stimulus experiments: nozzle up and down, dispensed volumes from 1 to 400 litres, both pump sides, every dip-switch variant. Enough coverage to separate framing from payload with confidence rather than a hunch.

The protocol turned out to be 25-byte frames at 38400 8N1, delimited by 0x04 and 0x06, carrying device-ID prefixes and BCD-encoded fields.

Cycle 1, nozzle down. Pump face: 743.45 total · 1.19 litres · 624.75 per litre.
04 02 28 43 91 78 00 24 75 00 26 03 78 FC 00 43 45 01 19 2A 07 00 03 42 06 04 20 05 04 04 20 05 04 04 21 05 04 02 28 43 91 78 00 24 75 00 26 03 78 FC 00 43 45 01 19 2A 07 00 03 42 06
Transaction frames in copper, idle polls dimmed. 43 45 and 01 19 are 743.45 and 1.19 in BCD.

03 Surviving the line

Decoding it once is not the same as reading it forever.

The bus is shared with legacy equipment, so a clean frame is the lucky case rather than the normal one. The C++ framing layer validates delimiters and device prefixes and resynchronises byte by byte through corruption — recovering cleanly where fixed-offset parsing had been silently returning garbage that looked like data.

One failure took real hunting: a 0x88 bus-clearing broadcast from another device that was quietly corrupting calibration.

Then the per-site problem. Every forecourt wires its dispenser slightly differently, and hand-configuring each install does not scale. So the device works its own mapping out: it samples a stable idle baseline across 15 frames, detects the start signal, and scores every candidate byte permutation against BCD validity, monotonicity and the measured price-per-unit — taking the lowest-error mapping inside a 1% tolerance. The map self-expands at runtime as more digits activate. Per-site manual setup was removed entirely.

04 Epsilon1

A board that had to survive a forecourt.

Two-layer FR4, 120 mm square, with the interface on the front and the electronics on the back. Custom symbols and footprints drawn from the module datasheets. Ground poured on both layers. Termination fitted only if the unit is the last node on the bus.

It ships as an orderable package: Gerbers, drill files, and a BOM carrying LCSC part numbers. Silkscreened EPSILON1 v1.0 on the front.

MCU
ESP32-S3-DevKitC-1Arduino framework, USB-C facing the board edge
Display
4″ MSP4031 TFT, touchLovyanGFX, portrait, front-left
RFID
RC522, front top-rightAntenna keepout on both layers
Dispenser link
RS485 → 3-pin screw terminalA / B / GND out to the pump
Receipts
Thermal printerQR code resolving to the online record
Storage
SD cardCrash-safe flash queue with idempotent event IDs
Buses
Two SPI, I²C, two UARTs, USB-CDCFive peripherals sharing rails and arbitration
Protection
PTC fuse, reverse-polarity MOSFET, 1500 W TVS1.1 A hold / 2.2 A trip on the 5 V front end

05 The system

The sale never waits for the network.

Connectivity on a forecourt is not dependable, and a customer holding a nozzle will not wait for a retry. Every transaction is committed to the SD card first and queued; the cloud drains that queue whenever it can reach it. If the link is down for a day, the terminal does not notice and neither does the customer.

One RFID reader does two jobs — vehicle and card loyalty at the pump, and staff attendance at shift change — because the hardware was already on the board and the second job cost a firmware module.

Prices move. Changing a price per litre used to mean a site visit; it is now a configuration push, over the air.

Live monitor
Per-minute transaction monitoring, read straight off the dispenser frames
Offline queue
Local commit first, cloud sync secondSD-backed, drains on reconnect
Ledger
Daily transaction logs and account audit, readable anywhere
Loyalty
Points per vehicle and per card
Attendance
Per-employee, same reader
Receipts
Printed on demand with a verifiable QR code
On-device
Touchscreen history, loyalty balance and attendance logThe attendant does not need the dashboard to answer a customer
Config
OTA, including price per litre and per-pump settings
Firmware
~8,700 lines of C++ on ESP32-S3Running on live dispenser hardware in production
Toolchain
~1,500 lines of PythonBus capture, calibration fitting, failure analysis, and a live analytics dashboard
Calibration
Versioned profiles persisted as JSONByte-index maps, BCD field lengths, decimal placement, nozzle-state thresholds — a device can be re-flashed and restored

06 Where it landed

Every litre has a record, and the record has a receipt.

The owner sees transactions as they happen rather than as a number at the end of a shift. Customers leave with paper that can be checked against the ledger. Loyalty accrues without anyone remembering to do anything, and attendance stopped being a separate book.

The engineering that made it possible was not the dashboard. It was two weeks of somebody willing to sit with a logic analyser and a nozzle.

Epsilon Labs owns the board design and the firmware. The client owns their deployment, their data and their ledger.

How we do hardware