When I started building Yambiro, my capstone predictive-maintenance system, the obvious architecture was to stream sensor data to the cloud and run the models there. In a Zimbabwean industrial context, that assumption breaks fast: connectivity is intermittent, bandwidth costs money, and a dropped link at the wrong moment means a missed failure.
So I flipped it. The intelligence lives on the machine.
The finished system monitors two 230 V ball-bearing AC fans — one kept healthy, one intentionally unbalanced — using a pair of BLE vibration/environment sensors and a custom-firmware ESP32 gateway. It fuses both sensor streams into a single feature window, runs a neural network classifier entirely on-device, and reaches 99.3% validation accuracy with an end-to-end fault-detection latency of under three seconds — all without a single always-on cloud inference call.
Two gateways, one job each
The hardware is built around two MOKO ESP32-WROOM-32E gateways, each doing a distinct job:
- MK110 Plus 01 — passively scans both BLE sensor beacons at 2 Hz, runs the on-device classifier, reads energy telemetry off a BL0942 metering IC, and drives the first relay.
- MK107D Pro-35D — controls the second fan’s relay channel over MQTT and mirrors state feedback.
Both gateways publish classification results, raw telemetry, energy readings, and relay states to a cloud MQTT broker — but only as small confirmations of decisions already made locally, never as the trigger for them.

The complete power and control schematic: dual mains-isolated 3.3 V/5 V rails feed two ESP32-WROOM-32E modules, each driving its own relay and RGB status LED.
On the bench, that circuit becomes a physical two-fan test rig:

The prototype: two 230 V ball-bearing fans (A, C) powered through a shaver-socket isolation transformer (D); relay-switched gateways (E, F) control each fan independently; B is the mains interface. After an 8-hour continuous run on a stable LAN, there were zero MQTT disconnects.
Fusing two sensors into one decision
Each BLE beacon broadcasts an advertisement roughly twice a second, carrying six features: temperature, humidity, and the three-axis vibration RMS. Every ~500 ms, the gateway merges the latest readings from both sensors into a single 12-feature row. Ten consecutive rows form a 5-second, 120-value window — the exact tensor shape the model expects.
Here is the shape of that fusion-and-inference loop on the microcontroller:
// Runs on the MK110 ESP32 gateway, close to the machines
void loop() {
BeaconFrame s1 = scanSensor(SENSOR_1_MAC); // temp, humidity, vib X/Y/Z
BeaconFrame s2 = scanSensor(SENSOR_2_MAC);
float row[12];
fuseFeatures(s1, s2, row); // 12 features/row, alphabetical, S1 then S2
pushToWindow(row); // rolling 10-row x 12-feature window
if (windowReady()) {
float scores[2] = model.classify(window); // on-device inference, ~30-80ms
Verdict v = debounce(scores); // 5-of-5 vote before a state can flip
if (v.changed) {
setRelay(v.state == HEALTHY); // isolate the fan before damage
publishFusion(v); // small JSON: sensor_id, prediction, confidence
}
}
delay(SAMPLE_INTERVAL_MS);
}
Choosing accuracy over footprint
I trained a dense neural network classifier in Edge Impulse on more than an hour of real dual-sensor telemetry recorded on the actual test rig — no synthetic data — for 6,060 training windows over 30 epochs. Two export variants were on the table:
| Variant | Validation accuracy | Verdict |
|---|---|---|
| Int8 quantised | 75.8% | Rejected — insufficient class separation |
| Float32 | 99.3% | Deployed |
Because the classifier’s output directly gates a 230 V relay, I chose the heavier float32 model over the smaller quantised one. Linking the float32 Edge Impulse library also pushed the firmware past the ESP32’s default 1.2 MB application partition, which meant switching to a Minimal SPIFFS (1.9 MB app + OTA) partition scheme on the 4 MB flash device — a small trade-off for a model I could trust to control mains power unsupervised.
Keeping the classifier honest: the debouncer
Raw per-window predictions are noisy at the boundary between healthy and unbalanced, so a state change is never acted on from a single inference. Every verdict passes through a voting debouncer first:
- A rolling window of the last 5 votes is kept per gateway.
- On-device, a state only becomes eligible to commit once 5 of 5 votes agree (the backend collector uses a looser 3-of-5 majority).
- A commit only fires when the new state differs from the currently committed one — so messages go out only on true transitions, not on every inference.
- Relay rule: Healthy keeps the relay closed; Unbalanced keeps it open and raises the alert.
This is what turns a jittery classifier into a system I’m comfortable letting isolate live mains-powered equipment on its own.
Why a phone app, not a browser dashboard
The original plan was a browser-based monitoring website. I moved it to a native Flutter mobile app instead, for reasons that only became obvious once I thought about who actually uses this on a plant floor: technicians need machine health at the machine, not tied to a desk; the single most important requirement turned out to be an instant push notification the moment the AI enters an “Unbalanced” state; and operators needed to actuate relays directly from their pocket, not just look at a chart. Flutter also meant one codebase targeting Android, iOS, and desktop.
The live dashboard: a composite bearing-health gauge, per-machine cards for Gateway A/B with editable names, and real-time power, voltage, and current from the BL0942 energy module.
Safety Mode: turning a fault into a decision, not a surprise
The most important piece of UX in the whole app is what happens after a fault is detected. A single bad classification shouldn’t shut down a machine — but a confirmed one shouldn’t be ignored either. I modelled this explicitly as a state machine:

The Safety Mode controller runs once per snapshot. A bad classification while a relay is on doesn’t trip an immediate shutdown — it starts a 15-second fault-confirming hold to reject vibration transients, then a 10-second alerting countdown the operator can ignore, before the system finally publishes a relay-off command.
Walking through one real fault cycle on the dashboard:
The fault confirms, the “Due for Maintenance” banner starts blinking with a haptic buzz and a countdown, the operator gets an “Ignore” option to keep the line running if it’s a known transient, and — if untouched — the countdown reaches zero and the app publishes the relay-off command automatically.
If the operator does nothing, Yambiro shuts the affected relay down on its own and, if Standby is enabled, can bring the alternate machine online in its place — chosen by whichever has the lower health score.
Turning telemetry into a to-do list
Detecting a fault is only half the job; a maintenance team needs to know what to actually do about it. On every snapshot refresh, an eight-rule recommendation engine evaluates the fused telemetry in parallel — critical rules for a health score under 50% with a relay energised, high-priority rules for a ≥20% vibration delta between machines or repeat fusion faults, and medium-priority rules for cumulative runtime and energy exposure. Each fired rule produces a ranked, justified recommendation (Preventive / Corrective / Diagnostic) with no separate maintenance-management system required.
The Insights hub surfaces those ranked recommendations alongside AI stability, health trend, and historical 7-day data — the same telemetry that trained the model now drives the maintenance schedule.
The numbers
- Validation accuracy: 99.3% (float32), n = 1,212 held-out samples.
- End-to-end latency: under 3 seconds, physical disturbance to dashboard alert — versus an estimated 8+ seconds for an equivalent cloud round trip.
- Bandwidth per state change: roughly 60–80 bytes of JSON, versus streaming thousands of raw coordinates a minute in a cloud-only design. During steady healthy operation, the system generates zero prediction traffic at all.
- Reliability: zero MQTT disconnects across an 8-hour continuous bench run on a stable LAN.
Why this matters
Edge AI is not about having less capability. In the environments I build for, it is what makes the capability real. For Zimbabwe’s manufacturing sector — and more broadly for Africa, where connectivity costs, import cycles, and skilled-support availability are all persistent constraints — an architecture like Yambiro’s shows that inexpensive ESP32 gateways, commodity BLE sensors, and open tooling like Edge Impulse can deliver industrial-grade classification accuracy from a phone already in an operator’s pocket, without depending on a data centre hundreds of kilometres away.
The same philosophy carried straight into Gwenya, my smart energy adapter — isolate the appliance before the surge damages it, using logic that lives on the device itself.