Tiny Online Learning with Human-in-the-Loop for Industrial Condition Monitoring
Deploy predictive maintenance on Day 1. No training data. No ML expertise. System learns faults as operators discover them.
graph LR
A[27% PdM Adoption] --> B[Expertise Gap]
A --> C[Vendor Lock-in]
A --> D[Integration Cost]
B --> E[TinyOL-HITL]
C --> E
D --> E
E --> F[Deploy Immediately<br/>K=1 → K=N]
stateDiagram-v2
[*] --> NORMAL: K=1 baseline
NORMAL --> FROZEN: Outlier detected
FROZEN --> NORMAL: Operator labels → K++
FROZEN --> NORMAL: Operator discards
- Day 1: Everything = "normal" (K=1)
- Fault occurs: Device freezes, alarms SCADA
- Operator inspects: Labels fault type
- System learns: K=2, correct predictions next time
# 1. Clone
git clone https://github.com/leekaize/tinyol-hitl.git
cd tinyol-hitl
# 2. Configure
cp core/config.template.h core/config.h
# Edit WiFi credentials
# 3. Upload (Arduino IDE)
# Board: ESP32 Dev Module or Raspberry Pi Pico 2 W
# Open: core/core.ino
# Upload
# 4. Wire sensor
# ADXL345/MPU6050 → I2C (SDA=21, SCL=22 for ESP32)// Core
kmeans_init(&model, 3, 0.2f);
int8_t cluster = kmeans_update(&model, features);
kmeans_add_cluster(&model, "fault_name");
// Persistence (NEW)
storage.begin();
storage.load(&model); // On startup
storage.save(&model); // After add_cluster
storage.clear(); // On reset commandSee docs/API.md for complete reference.
flowchart TB
subgraph Device[MCU - 2.5KB]
A[Accelerometer] --> B[Feature Extraction]
B --> C[Streaming K-means]
C --> D{Outlier?}
D -->|No| E[Update Centroid]
D -->|Yes| F[FREEZE]
end
subgraph SCADA[FUXA Dashboard]
G[Alarm Banner]
H[Label Button]
I[Gauges]
end
F --> G
H --> F
E --> I
Trained clusters survive power cycles.
flowchart LR
A[Label Fault] --> B[K++]
B --> C[💾 Save to Flash]
C --> D[Power Off]
D --> E[Power On]
E --> F[Load from Flash]
F --> G[Resume K clusters]
When saves happen: Immediately after each new cluster
Reset model: mosquitto_pub -t "tinyol/{device_id}/reset" -m '{"reset":true}'
Platform storage:
- ESP32: NVS (Preferences library)
- RP2350: LittleFS
| Component | Size |
|---|---|
| Clusters (16 max) | 1.0 KB |
| Ring buffer (100 samples) | 1.2 KB |
| Metadata | 0.3 KB |
| Total | 2.5 KB |
| Dataset | Method |
|---|---|
| CWRU Bearing | 4 fault classes, streaming via Serial |
| Real Motor | 0.5HP 3-phase, eccentric weight faults |
TO-DO: Actual benchmark results in progress. See docs/SPRINT_2DAY.md.
core/ # Arduino sketch
├── core.ino # Main loop
├── streaming_kmeans.c # Algorithm (200 lines)
└── config.template.h # WiFi/MQTT settings
docs/
├── presentation/ # sli.dev slides
├── API.md # Function reference
└── SPRINT_2DAY.md # Current sprint
data/datasets/cwru/ # Benchmark dataset pipeline
integrations/ # FUXA SCADA setup
cd docs/presentation
npm init slidev@latest
npm run dev
# http://localhost:3030Apache-2.0
@misc{tinyol-hitl2025,
author = {Lee, Kai Ze},
title = {TinyOL-HITL: Unsupervised TinyML Fault Discovery},
year = {2025},
publisher = {GitHub},
url = {https://github.com/leekaize/tinyol-hitl}
}