Delivered remotely for a business based in Dublin, Ireland. Names withheld by agreement.
Project Overview
Industry: Fleet and site fuel management — the operational point where fuel is physically transferred into a vehicle or plant asset.
Type of solution: A cross-platform mobile application for field operators, built from a single codebase for native mobile and web delivery, consuming a REST backend and integrating directly with dispensing hardware over Bluetooth Low Energy.
Business context: Fuel is among the largest controllable costs in any fleet or site operation, and most of what is lost is lost in the recording rather than in the tank. Three facts have to be captured at the moment of transfer — which asset received the fuel, how much went in, and what that asset's odometer or engine-hour reading was at the time. Captured on paper, or from memory at the end of a shift, they drift apart. Once they have drifted, consumption per asset is unusable, which means maintenance intervals are guesses and cost allocation is fiction.
General users: Field operators — fuel-truck drivers, depot attendants and site refuellers. Administrative and reporting users work in the backend platform rather than in this application.
General purpose: To make the fuel transfer itself the recording event. Nothing can be dispensed until the asset and its meter reading are captured, and the quantity recorded comes from the equipment rather than from a person.
The Business Challenge
The record is made after the fact, so it is wrong. An operator fuelling a dozen assets across a shift is reconstructing details at the end of it. The volume is approximate, the meter reading is estimated, and occasionally the asset is simply the wrong one.
Consumption data is only as good as the meter reading beside it. Litres alone say nothing. Litres against a distance or engine-hour reading is the number the business actually needs, and it is the half most likely to be missing or invented.
Different assets are measured differently. A road vehicle is measured in distance; a generator, excavator or pump is measured in engine hours. The application cannot assume one, and asking the operator to know which is asking them to get it wrong.
Refuelling happens where there is no signal. Depots, quarries, remote sites, yards between buildings. An application that requires connectivity to do its job will be worked around within a week, and the paper process comes straight back.
The transfer takes minutes, and phones do not always survive them. Fuel flows for as long as it flows. If the application is closed, backgrounded out of memory or the phone dies mid-transfer, the transaction cannot be lost — fuel has physically moved and something must account for it.
Physical equipment does not respond on a software timescale. A start command may take a long time to be acknowledged, or may never be. The interface has to remain honest and recoverable when the equipment does not answer.
The environment is hostile to interfaces. Sunlight, gloves, one hand on a nozzle, noise. Anything requiring precision or attention will be skipped.
Operational rules differ between deployments. Whether a work-order reference is required, and which dispensing points a given operator may use, vary by site and by customer — and cannot require a new release each time.
Our Approach
Make the workflow gate the equipment, not follow it. The sequence is deliberate: authenticate, choose a dispensing point, identify the asset, capture its meter reading, and only then start the transfer. The gate is the point of the product. By the time fuel moves, everything the business needs to attribute it has already been captured, and the operator has not been asked to remember anything afterwards.
Take the quantity from the equipment. Dispensed volume and flow-meter telemetry are read from the dispensing controller and displayed live during the transfer. The recorded figure is the metered figure. There is no field in which a person types how much they think went in.
Treat no-signal as a supported mode, not a failure. The application checks connectivity at the moment the operator signs in and, finding none, routes directly into a Bluetooth path that talks to the dispensing controller without any server involvement. This is deliberately not offline queueing: the operator's actual task completes rather than being recorded for later reconciliation.
Hold transaction state on the server so the client can be disposable. The dispensing transaction is owned by the backend. When an operator authenticates, any unfinished transfer comes back with their session and the application re-enters the live screen and resumes following it. A phone that crashed mid-transfer recovers by being unlocked and reopened.
Bound every operation against physical equipment. Both starting and stopping a transfer are wrapped in explicit timeouts with a defined outcome. Equipment that never answers produces a recoverable state and a clear message rather than a spinner the operator eventually force-quits.
Build the form from the server's requirements. Whether an asset needs a distance or an engine-hours reading, and whether a work-order reference is mandatory, are both returned by the backend. The operator is asked for exactly the right thing without knowing why, and a customer with different rules is a configuration change rather than a release.
Reduce each screen to one action. A single large primary button, colour-coded and re-labelled as the state changes, with the current asset, meter reading and live volume above it. Nothing else on the screen during a transfer, and back navigation disabled while fuel is flowing.
One codebase, every target. The application is built with a cross-platform framework that compiles the same source to native mobile applications and to web, so a device fleet is served from a single release cycle.
The Solution
Operator sign-in with assigned dispensing points. Authentication returns the operator's identity, session and the specific dispensing points they are permitted to use, with unavailable points shown but not selectable.
Consent gate. A privacy agreement is presented and must be accepted before first use, with the full agreement readable in the application.
Multilingual interface. Language can be changed at any point from any screen, with the choice persisted across sessions — a practical necessity for a mixed field workforce.
Asset identification. The operator enters the asset number, which is confirmed against the backend. The response carries the asset's identity, its current and previous meter values, and which class of meter reading it requires.
Adaptive meter reading capture. The form renders a distance reading or an engine-hours reading according to the asset's requirement, with validation to match, plus an optional work-order reference where the deployment requires one.
Live dispensing session. Starting the transfer opens a monitored session showing dispensed volume and flow-meter telemetry updating throughout, with the asset and meter reading held on screen for confirmation and a clear safety instruction and caution notice.
Automatic and manual completion. The session ends automatically when the equipment signals that the transfer is complete, or immediately when the operator stops it. Both paths close the transaction the same way.
Transfer resumption. An unfinished transfer is returned at the operator's next sign-in and the application re-enters the live session and resumes following it, on the same device or another.
Direct equipment control without connectivity. Where there is no signal, the application discovers the dispensing controller over Bluetooth Low Energy, connects to it, subscribes to its telemetry and controls the transfer directly through a binary protocol, showing fill progress from the controller's own reporting.
On-site receipt printing. A thermal printing capability over the same Bluetooth transport, with formatted text, barcode and QR code output, so a paper record can be produced at the point of transfer.
Session integrity. Session expiry is detected centrally, clearing local state and returning the operator to sign-in with an explanation rather than failing silently mid-workflow.
Key Features
Enforced capture-before-dispense workflow The transfer cannot begin until the asset is identified and its meter reading captured, making the recording a precondition of the physical act rather than a task that follows it.
Metered volume from the equipment Dispensed volume and flow-meter telemetry are read live from the dispensing controller during the transfer, so the recorded quantity is measured rather than reported.
Adaptive meter capture by asset class Distance or engine-hours capture is selected by a requirement returned with the asset, so each asset is measured correctly without the operator having to know which applies.
Offline direct equipment control Where there is no network, the application connects to the dispensing controller over Bluetooth Low Energy and drives the transfer directly, so the operator's task completes rather than being deferred.
Server-held transactions with client resumption An unfinished transfer is returned at the next sign-in and the application resumes following it, so a crashed or dead phone does not lose fuel that has physically moved.
Watchdog-bounded equipment operations Start and stop are both bounded by explicit timeouts with defined outcomes, so unresponsive equipment produces a recoverable state rather than an indefinite wait.
Single-action field interface Each screen reduces to one large primary action, colour-coded and re-labelled as the state changes, with back navigation disabled during an active transfer.
Server-driven operational configuration Permitted dispensing points, meter requirements and optional work-order capture are all returned by the backend, so deployments with different rules are configured rather than rebuilt.
Runtime language switching The interface language can be changed at any point and is remembered, supporting a mixed workforce without separate builds.
On-site thermal receipt printing Formatted receipts including barcode and QR output can be produced over the same Bluetooth transport at the point of transfer.
Technical Architecture
Client application. A cross-platform mobile client built with a modern reactive JavaScript framework and typed throughout, compiled from a single source to native mobile applications and to web delivery. Screens are organised as a linear workflow with file-based routing and shared layouts.
Workflow state layer. Each step's output is written into a small, single-purpose state store rather than passed through navigation parameters — session and identity, workflow selections, active transaction handle, and interface language held separately. Session and language are persisted to device storage; workflow state deliberately is not.
Network layer. A single HTTP wrapper behind a global request interceptor that attaches the API base address, query serialisation, request timeout, session token and client platform. Responses are classified into three distinct outcomes — transport failure, application-level business rejection and session expiry — each producing different behaviour rather than a single generic error.
Access control layer. A navigation interceptor enforces authentication on any screen that declares it requires one, applied uniformly across every navigation method rather than per-screen.
Live session layer. The dispensing screen runs three request lifecycles against one transaction — start, periodic telemetry poll and stop — deriving the entire interface state from polled telemetry through a reactive effect, with independent watchdog timers bounding the start and stop operations.
Direct equipment layer. An independent control path that bypasses the API entirely: a promise-based wrapper over the platform's Bluetooth Low Energy interface handling adapter initialisation, device discovery with signal strength, connection, service and characteristic enumeration, telemetry subscription with binary frame parsing, and command transmission. Device identifiers are persisted so reconnection skips rediscovery.
Printing layer. A thermal printer command builder with text styling, alignment, barcode and cut control, multi-codepage text encoding, and QR code rendering, sharing the Bluetooth transport.
Localisation layer. Message catalogues with runtime locale switching and a non-component translation helper so network and interceptor code can produce localised messages outside the component tree.
Flow: Operator sign-in → assigned dispensing point → asset identification → adaptive meter capture → transfer start → periodic telemetry → automatic or manual stop, with a parallel path of connectivity check → direct Bluetooth connection → controller telemetry and command → on-site receipt.
Technology Stack
| Category | Technology |
|---|---|
| Application framework | Vue 3 with the composition API, TypeScript |
| Cross-platform framework | uni-app, targeting native mobile and web from one codebase |
| Build tooling | Vite with per-environment configuration and production minification |
| State management | Pinia with a persisted-state plugin bound to device storage |
| Internationalisation | vue-i18n with runtime locale switching |
| Styling | UnoCSS atomic CSS with platform-specific presets, SCSS |
| UI components | Cross-platform component library for forms, inputs, overlays and notifications |
| Request lifecycle | Composable request hooks with polling and manual-trigger modes |
| Hardware integration | Bluetooth Low Energy through the platform API, wrapped in a promise-based device class |
| Peripheral output | ESC/POS thermal printer command builder with multi-codepage encoding and QR generation |
| Routing | File-based routing with layouts and declarative authentication requirements |
| Code quality | ESLint, Prettier, Stylelint, commitlint with staged-file hooks, static type checking |
| Backend interface | REST API with token-based session authentication |
Technical Challenges & Solutions
| Challenge | Our Approach |
|---|---|
| Refuelling happens where there is no mobile signal, and an application that stops working there gets abandoned | Connectivity is checked at sign-in and, where absent, the application routes into a direct Bluetooth path that controls the dispensing controller with no server involvement — the operator's task completes rather than being queued for later reconciliation. |
| Fuel keeps flowing even if the phone does not | The dispensing transaction is owned by the backend and returned with the operator's session at next sign-in, so the application re-enters the live screen and resumes following an unfinished transfer — on the same device or a different one. |
| The client sees a continuous physical process only as periodic snapshots | The live screen derives its entire state — started, running, complete — from polled telemetry through a single reactive effect combining volume, flow-meter readings, fill progress and the equipment's own completion signal, rather than from local assumptions about timing. |
| Physical equipment does not respond on a software timescale | Both start and stop are bounded by explicit watchdog timers with defined fallback states, so unresponsive equipment degrades into a recoverable, clearly labelled UI state instead of an indefinite wait. |
| Different assets must be measured in different units, and operators should not have to know which | The meter requirement is returned with the asset and the capture form is constructed from it, rendering either a distance or an engine-hours field with matching validation. |
| Operational rules differ between deployments and cannot require a release each time | Permitted dispensing points, meter requirements and optional work-order capture are all server-returned configuration delivered with the session, so a new deployment's rules are configured rather than coded. |
| Interfaces used outdoors, in gloves, one-handed, holding a nozzle | Each screen reduces to a single large primary action, colour-coded and re-labelled by state, with the safety instruction and live figures above it and back navigation disabled while fuel is flowing. |
| Driving a hardware controller through a callback-based, device-inconsistent platform API | The Bluetooth interface is isolated behind a promise-based device class handling adapter state, discovery, service and characteristic enumeration, subscription and transmission, with identifiers persisted for silent reconnection. |
| One workforce, multiple languages, one build | Message catalogues with runtime switching from any screen and a persisted preference, plus a translation helper usable outside the component tree so network-layer messages are localised too. |
Security & Reliability
Token-based sessions. Authentication returns a session token attached to every subsequent request by a single global interceptor rather than per call, so no request path can accidentally omit it.
Centralised session-expiry handling. Expiry is detected in one place in the network layer, which clears local state and returns the operator to sign-in with an explanation — rather than surfacing as an unexplained failure part-way through a workflow.
Declarative route protection. Screens declare whether they require authentication, and a navigation interceptor enforces it across every navigation method uniformly.
Explicit consent. A privacy agreement is presented and must be accepted before first authentication, with the full text readable in the application.
Server-held transaction state. Because the transaction lives on the backend rather than on the device, a lost, broken or replaced phone does not lose a record of fuel that has physically moved.
Bounded physical operations. Watchdog timers around start and stop guarantee that the interface reaches a defined, recoverable state regardless of whether the equipment responds.
Interference protection during a transfer. Back navigation is disabled at the platform level while fuel is flowing, so a transfer cannot be abandoned by accident mid-operation.
Separated error semantics. Transport failure, application-level business rejection and session expiry are handled as three distinct outcomes, so operators are told what actually happened rather than being shown one generic message.
Scalability & Performance
Small by construction. Few dependencies, no heavy client-side libraries, atomic CSS generated only for what is used, and production builds minified with development logging stripped — appropriate for devices that are often not new.
Bounded network behaviour. The client holds one active transaction and no list data of any size, and telemetry polling runs at a fixed interval independent of load, so per-session request volume is predictable and capacity planning is a simple function of concurrent dispensing sessions.
Disciplined timer and lifecycle management. All timers are cleared on unmount and polling is cancelled on completion, so a long shift of consecutive transfers does not accumulate background work.
Minimal persistence. Only session and language preference are written to device storage; workflow state is held in memory, keeping storage access infrequent.
Fast Bluetooth reconnection. Device, service and characteristic identifiers are persisted after first pairing, so subsequent connections skip discovery entirely — meaningful when an operator reconnects to the same controller many times a day.
Single release cycle across platforms. Because native and web targets build from one codebase, rolling an update to a device fleet is one release rather than several.
Compatibility with older devices. A runtime polyfill for missing platform methods is applied selectively at startup instead of shipping a broad compatibility bundle, keeping the payload small while supporting older hardware still in service.
Business Outcomes
- The record is created at the moment of transfer, not reconstructed at the end of a shift, because dispensing cannot begin until the asset and meter reading are captured.
- Recorded volumes are metered volumes, read from the dispensing equipment rather than entered by a person afterwards.
- Every transfer carries a usable meter reading, in the unit appropriate to that asset, which is the pairing that makes consumption-per-asset analysis possible at all.
- Refuelling continues where there is no signal, through direct equipment control, so the paper fallback does not need to exist.
- Interrupted transfers are not lost, because the transaction is held server-side and resumed at the operator's next sign-in.
- Operators are asked for exactly what each asset requires, with the form determined by server-returned configuration rather than by operator knowledge.
- New deployments with different operational rules are configured rather than rebuilt, since permitted dispensing points and capture requirements arrive with the session.
- A mixed-language workforce uses one application, with language switchable at any point and remembered.
Why it worked
Software that controls physical equipment is a different discipline from software that manages records. The failure modes are not exceptions to be caught — they are a valve that did not answer, a phone that died with fuel flowing, a depot with no coverage, a pair of gloves that cannot hit a small button. None of those are edge cases in this environment. They are Tuesday.
Our team builds for that reality. We put the recording before the physical act rather than after it, because a workflow that asks for data once the fuel is already in the tank is a workflow that gets skipped. We treated no-connectivity as a supported mode with its own control path rather than as an error state with a queue, because operators judge a tool by whether their job finished. We put the transaction on the server so the device could be disposable. And we bounded every operation against physical equipment with an explicit timeout and a defined outcome, because the fastest way to lose a field workforce is to show them a spinner they cannot escape.
Our teams work across cross-platform mobile development, hardware integration over Bluetooth Low Energy and serial protocols, REST API integration, offline-capable architectures and multilingual field applications — with the operational judgement to know which parts of a physical process the software must control and which it should only observe.
Final Summary
Fuel management fails at the last metre. Whatever the reporting platform can do, the numbers reaching it are only as good as what an operator recorded standing next to a running nozzle — and standing next to a running nozzle, with a clipboard, at the end of a long shift, is where accuracy goes.
Our team built the mobile application that closes that gap. The operator signs in, selects a dispensing point they are permitted to use, identifies the asset and enters its meter reading — in distance or engine hours, whichever that asset requires, determined by the system rather than the operator — and only then can fuel move. Throughout the transfer the screen shows the volume and flow-meter telemetry reported by the equipment itself, and the session ends when the equipment says it has, or when the operator stops it. The figure that reaches the business is measured, not remembered.
Two decisions carry the product beyond that. The transaction is owned by the backend, so a phone that crashes mid-transfer is recovered by unlocking it — the unfinished transfer comes back with the next sign-in and the application picks it up where it stopped. And because refuelling happens in quarries, depots and yards where signal does not reach, the application can abandon the network entirely and drive the dispensing controller directly over Bluetooth, complete the transfer and print a receipt on the spot. Delivered from a single cross-platform codebase, with a multilingual single-action interface built for sunlight and gloves, it makes the accurate record the easiest available path — which, for a field tool, is the only way one ever gets used.