Arduino Simulation Demo
GenesisNode running as a complete edge AI system on Arduino-class microcontrollers. The demo simulates a real-world predictive maintenance scenario: a DC fan with an accelerometer detects vibration imbalance and sends alerts over MQTT. Everything runs on-device.
The GenesisNode inference engine -- node structs, connection graph, signal propagation, tokenizer -- fits in as little as 1.1KB of SRAM and executes in ~4 microseconds on an ESP32-S3. There is no cloud inference, no WiFi round-trip for predictions, no TensorFlow Lite. The model trains and infers entirely on the microcontroller.
The browser-based simulation is a faithful JavaScript port of the C implementation (genesisnode_nano.h). The same node struct layout, the same signal propagation algorithm, the same tokenizer thresholds. Board-specific performance estimates are derived from cycle-accurate operation counting against real hardware profiles (clock speed, multiply latency, I2C bus timing, OLED refresh cost).
The simulation covers the full lifecycle: start a fan, collect accelerometer data, train a model to distinguish normal vs. imbalanced vibration, switch to inference mode, and watch the system detect faults in real time with MQTT alerts firing on every detection.
The Demo Flow
-
Start the Fan
Click Start Fan. The four-blade fan visualization begins spinning, and the simulated MPU-6050 accelerometer starts generating vibration readings at 200 Hz. The vibration chart shows low-amplitude, regular sinusoidal motion -- a healthy, balanced fan.
-
Train the Normal Baseline
Click Train: Normal. The system collects 100 accelerometer windows (50 samples each), tokenizes them into vibration descriptors (
vib_low,vib_stable), and trains the GenesisNode model. Watch the node graph populate as nodes are created for each token and connected to thenormallabel. The progress bar tracks training completion. On an ESP32-S3, all 100 training steps would take about 1.7 ms total. -
Add Weight to a Blade
Click Add Weight. A red dot appears on blade 1, and the fan animation switches from smooth rotation to a wobbling motion. The accelerometer chart immediately shows higher-amplitude, irregular vibration. RMS magnitude jumps from ~0.02g to ~0.15g.
-
Train the Imbalance Pattern
Click Train: Imbalance. The system collects 100 imbalanced vibration windows and trains them against the
imbalancelabel. The node graph now shows both labels with weighted connections. Tokens likevib_high,vib_burst, andvib_spikeget strong connections toimbalance, whilevib_lowandvib_stableconnect strongly tonormal. -
Switch to Inference Mode
Click Inference Mode. The system begins classifying every vibration window in real time. The prediction, confidence score, and running accuracy appear in the Inference Results panel.
-
Watch Real-Time Detection
With the fan running imbalanced, the system detects
imbalanceand the OLED mockup shows!! IMBALANCE !!in red. Toggle the weight on and off to see detection switch betweennormalandimbalance. The accuracy chart tracks classification performance over time, typically reaching 100% after the initial window fills. -
See MQTT Alerts
Every imbalance detection triggers an MQTT message. The MQTT panel shows timestamped alerts with confidence scores. The serial monitor shows the full JSON payload as it would appear on a real device's serial output at 115200 baud.
Board Selector
The board dropdown in the top bar changes all performance estimates to reflect real hardware characteristics. Six boards are available:
Arduino Nano V3 (ATmega328P)
- Clock: 16 MHz 8-bit AVR
- SRAM: 2 KB
- WiFi: No (serial bridge to PC for MQTT)
- Max nodes: 24 (model fits, but barely -- the GenesisNode struct consumes ~1.1KB of the 2KB SRAM, leaving ~400 bytes for Arduino core, Wire, Serial)
- Inference time: ~115 us
- Best for: Proving the concept runs on the smallest possible hardware
XIAO SAMD21 (Cortex-M0+)
- Clock: 48 MHz ARM
- SRAM: 32 KB
- WiFi: No (serial bridge)
- Max nodes: 255
- Inference time: ~25.6 us
- Best for: Prototyping with real hardware you already own
ESP32-S3 (Xtensa LX7 dual-core) -- TOP PICK
- Clock: 240 MHz
- SRAM: 512 KB + 8 MB PSRAM
- WiFi: Yes, plus BLE 5
- Max nodes: 7,977
- Inference time: ~4 us (250K inferences/sec)
- Best for: The recommended production board. Native WiFi for direct MQTT, dual-core (one for inference, one for networking), enough memory for GenesisNode's full three-tier architecture
ESP32-C3 SuperMini (RISC-V)
- Clock: 160 MHz
- SRAM: 400 KB
- WiFi: Yes, plus BLE 5
- Max nodes: 5,957
- Inference time: ~6-8 us (143K inferences/sec)
- Best for: Cheapest WiFi-capable option at $2-4 per board. Ideal for deploying a fleet of sensor nodes
Pi Pico 2 W (Cortex-M33 dual-core)
- Clock: 150 MHz
- SRAM: 520 KB
- WiFi: Yes, plus BLE 5.2
- Max nodes: 7,744
- Inference time: ~5-7 us (167K inferences/sec)
- Best for: Raspberry Pi ecosystem, excellent documentation, official board support
Teensy 4.0 (Cortex-M7)
- Clock: 600 MHz
- SRAM: 1 MB
- WiFi: No
- Max nodes: 15,955
- Inference time: ~1.1 us (909K inferences/sec)
- Best for: Maximum raw inference speed. Sub-microsecond inference, but requires external WiFi module for MQTT
Selecting a board updates all metrics in the Device Performance panel: SRAM usage percentage, maximum node capacity, inference time, inferences per second, full loop time (including I2C sensor reads, OLED refresh, and serial transmission), and total training time.
What the UI Shows
The interface is a three-panel layout simulating the full hardware stack.
Left Panel: Hardware Simulation
- Fan Motor: Animated four-blade fan with smooth spinning (balanced) or wobbling (imbalanced) animation. A red dot marks the added weight on blade 1.
- MPU-6050 Accelerometer: Real-time vibration chart (XY magnitude) with RMS and peak readouts. Sample rate is 200 Hz. Balanced vibration shows ~0.02g RMS; imbalanced shows ~0.15g with spikes.
- BME280 Environment: Simulated temperature and humidity readings. These can contribute
temp_highortemp_lowtokens for environmental context. - Tokens: The current set of tokens being fed to the GenesisNode model, displayed as colored tags. Examples:
vib_low,vib_stable,vib_high,vib_burst,vib_spike.
Center Panel: GenesisNode Engine
- SSD1306 OLED Display: Mockup of the 128x64 OLED that would sit on the physical device. Shows mode (IDLE/TRAINING/INFERENCE), status (Normal/IMBALANCE!), current tokens, and inference time with node and connection counts.
- Mode Control: Buttons for training (Normal/Imbalance), inference mode, and model reset. Configurable training sample count (10-1000). Progress bar during training.
- Node Graph: Live view of every node in the model. Shows token ID, connection count, connection targets, and weights. After training, you can see how
vib_highconnects strongly toimbalance(weight ~2000) whilevib_lowconnects strongly tonormal. - Device Performance: Board name, SRAM used vs. total, node count vs. maximum, inference time, inferences per second, full loop time with Hz, and estimated training time for 200 samples.
Right Panel: Output and Communication
- Inference Results: Prediction (normal/imbalance with color coding), confidence score, running accuracy percentage, total inference count, and accuracy-over-time chart.
- MQTT Messages: Topic (
genesisnode/fan/alert), broker info, alert count, and timestamped log of every imbalance detection with confidence and token data. - Serial Monitor: Scrolling terminal output styled like the Arduino IDE serial monitor at 115200 baud. Shows boot messages, training progress, inference results, and full MQTT JSON payloads.
LED Indicators
Two LED indicators in the top bar:
- Power LED (left): Green when the fan is running, off when stopped
- Status LED (right): Green during normal inference, red during imbalance detection, yellow during training, off when idle
The GenesisNode Engine
The inference engine running in the browser is a direct JavaScript port of genesisnode_nano.h, the C header designed for microcontrollers. The core data structures are identical.
Node Structure (44 bytes in C)
typedef struct {
char token_id[16]; // "vib_high", "normal", etc.
uint8_t n_conns; // number of connections (max 8)
GN_Connection conns[8]; // {target_index, weight} pairs (3 bytes each)
int16_t threshold; // firing threshold (default 200)
uint8_t maturity; // lifecycle stage
} __attribute__((packed)) GN_Node;
Each connection is 3 bytes: a uint8_t target index and an int16_t weight (fixed-point, divided by 1000). The entire node is 44 bytes packed.
The GenesisNode Instance
typedef struct {
GN_Node nodes[24]; // 24 nodes max on ATmega328P
uint8_t n_nodes;
GN_FireEntry firing_chain[16];
uint8_t chain_len;
uint8_t output_node;
int16_t output_score;
bool training_mode;
uint16_t total_inferences;
uint16_t total_train_steps;
} GenesisNode;
With 24 max nodes, the full struct is ~1,140 bytes. On an ATmega328P with 2KB SRAM, this leaves ~500 bytes for the Arduino core, Wire library, and Serial buffers. The fan vibration model typically uses 7-10 nodes, well within the 24-node limit.
Signal Propagation
Inference follows a two-pass algorithm:
- Input activation: Each input token is looked up by string match. Matching nodes get activation 1000. Their outgoing connections propagate signal:
activation[target] += activation[source] * weight / 1000. - Intermediate firing: All nodes above their threshold fire, propagating signal further through their connections.
- Label selection: The label node (normal/imbalance) with the highest activation wins.
The entire inference is integer arithmetic -- no floating point. The weight / 1000 division uses the fixed-point representation. This matters on AVR, which has no hardware floating-point unit.
Training
Training uses Hebbian-style weight updates:
- Connections from input tokens to the correct label are strengthened by the learning rate (default 80)
- Connections from input tokens to incorrect labels are weakened by half the learning rate
- Weights are clamped to [0, 2000]
Nodes and connections are created on demand via gn_get_or_create(). There are no pre-allocated layers or fixed topology.
Performance: Why It Is Fast
The fan demo model has ~7-10 nodes with ~8 connections each. Inference involves:
- ~7-10 string comparisons to find input nodes (token lookup)
- ~20-30 integer multiplications (signal propagation)
- ~2 string comparisons (label matching)
On an ESP32-S3 at 240 MHz with single-cycle multiply, this completes in ~4 microseconds. On an ATmega328P at 16 MHz with 17-cycle multiply, it takes ~115 microseconds. Either way, inference is a tiny fraction of the main loop -- I2C sensor reads (500 us on Nano) and OLED updates (20 ms) dominate.
MQTT Integration
Message Format
Every imbalance detection produces a JSON message:
{
"device_id": "fan-demo-001",
"event": "imbalance",
"confidence": 12000,
"tokens": ["vib_high", "vib_stable", "vib_burst"]
}
Fields:
device_id: Unique identifier for the sensor nodeevent: Detection type (imbalanceor could be extended to other fault classes)confidence: The activation score of the winning label (higher = more certain)tokens: The input tokens that triggered the detection, providing interpretability
Topic Structure
genesisnode/fan/alert -- imbalance detections
genesisnode/fan/status -- periodic heartbeat / health
genesisnode/fan/metrics -- inference timing, accuracy stats
Transport
- WiFi boards (ESP32-S3, ESP32-C3, Pico 2 W): Direct MQTT over TCP to a Mosquitto broker, AWS IoT Core, Azure IoT Hub, or any MQTT 3.1.1/5.0 broker.
- Non-WiFi boards (Nano, SAMD21, Teensy): JSON over serial at 115200 baud. A PC or Raspberry Pi running a serial-to-MQTT bridge (
mosquitto_puborpaho-mqttin Python) forwards messages to the broker.
Compatibility
The JSON payload format is directly compatible with:
- AWS IoT Core: Publish to an IoT topic, trigger Lambda, store in DynamoDB/TimeStream
- Azure IoT Hub: Device-to-cloud messages, route to Event Hub or Stream Analytics
- Home Assistant: MQTT integration, create binary sensors and automations
- Node-RED: MQTT input node, build dashboards and alerting flows
- Grafana: Via MQTT data source or InfluxDB sink
Building the Real Thing
Recommended Hardware (~$22 total)
| Part | Price | Notes |
|---|---|---|
| XIAO ESP32-S3 x1 | $7.49 | Primary demo board with WiFi/BLE |
| ESP32-C3 SuperMini 5-pack | $12-15 | Fleet sensor nodes at ~$2.50 each |
| Total | ~$20-22 |
You also need these sensors and peripherals (commonly available, ~$10-15 if you do not already have them):
- MPU-6050 accelerometer/gyro module (I2C, ~$2)
- BME280 temperature/humidity/pressure module (I2C, ~$3)
- 0.96" SSD1306 OLED display (I2C, 128x64, ~$3)
- DC motor + small fan blade (~$2)
- MOSFET or transistor for motor control
- EC11 rotary encoder (mode select / UI)
- MT6701 magnetic encoder (optional, for RPM measurement)
Wiring Diagram
All sensors share the I2C bus (SDA/SCL). On an Arduino Nano, that is A4 (SDA) and A5 (SCL). On XIAO boards, use the labeled SDA/SCL pins.
MPU-6050: SDA -> A4 SCL -> A5 VCC -> 5V GND -> GND (addr: 0x68)
BME280: SDA -> A4 SCL -> A5 VCC -> 3.3V GND -> GND (addr: 0x76 or 0x77)
SSD1306: SDA -> A4 SCL -> A5 VCC -> 5V GND -> GND (addr: 0x3C)
MT6701: SDA -> A4 SCL -> A5 (addr: 0x06)
DC Motor: D9 (PWM) -> MOSFET gate Motor+ -> 12V Motor- -> MOSFET drain
EC11 Enc: CLK -> D2 DT -> D3 SW -> D4 VCC -> 5V GND -> GND
Serial out: USB (built-in) -> PC running MQTT bridge
All four I2C devices coexist on the same bus at different addresses. No address conflicts.
The C Code
The complete inference engine is a single C header file: arduino/genesisnode_nano.h. It requires only <stdint.h>, <string.h>, and <stdbool.h> -- no Arduino-specific dependencies, no external libraries beyond the standard C library.
To use it in an Arduino sketch:
#include "genesisnode_nano.h"
GenesisNode gn;
const char *labels[] = { "normal", "imbalance" };
void setup() {
gn_init(&gn);
// ... sensor setup, I2C init, OLED init ...
// Run training (or load pre-trained weights)
}
void loop() {
// 1. Read MPU-6050 accelerometer
// 2. Fill 50-sample window
// 3. Tokenize vibration pattern
// 4. Run inference
uint8_t result = gn_infer(&gn, tokens, n_tokens, labels, 2);
// 5. Display on OLED
// 6. Send MQTT alert if imbalance detected
}
The arduino/sim_hardware.c file provides a complete desktop simulation with cycle-accurate timing estimates, simulated sensors, tokenizer, OLED output, and serial JSON alerting. Compile and run it on any system with a C compiler to verify the model works before flashing hardware:
gcc -O2 -o fan_demo arduino/sim_hardware.c -lm
./fan_demo
Performance Numbers
All estimates are derived from cycle-accurate operation counting in sim_hardware.c, using measured instruction costs for each architecture (e.g., 17 cycles per multiply on AVR, 1 cycle on ARM/RISC-V).
Inference (7-10 node model, 2-3 input tokens)
| Board | Inference Time | Inferences/sec | Notes |
|---|---|---|---|
| Arduino Nano V3 | 115 us | 8,700 | 16 MHz AVR, 17-cycle multiply |
| XIAO SAMD21 | 25.6 us | 39,000 | 48 MHz ARM, single-cycle multiply |
| ESP32-C3 SuperMini | 6-8 us | 143,000 | 160 MHz RISC-V |
| Pi Pico 2 W | 5-7 us | 167,000 | 150 MHz ARM Cortex-M33 |
| XIAO ESP32-S3 | 4.2 us | 238,000 | 240 MHz Xtensa LX7 |
| Teensy 4.0 | 1.1 us | 909,000 | 600 MHz ARM Cortex-M7 |
Memory Usage (24-node config for Nano, larger for other boards)
| Board | SRAM Total | GenesisNode Struct | Available After | Fits? |
|---|---|---|---|---|
| Arduino Nano V3 | 2,048 B | ~1,140 B (55.6%) | ~500 B | Yes (tight) |
| XIAO SAMD21 | 32 KB | ~1,140 B (3.5%) | ~31 KB | Yes |
| ESP32-C3 | 400 KB | scales with nodes | ~394 KB | Yes |
| ESP32-S3 | 512 KB + 8 MB | scales with nodes | ~511 KB+ | Yes |
| Pi Pico 2 W | 520 KB | scales with nodes | ~519 KB | Yes |
| Teensy 4.0 | 1 MB | scales with nodes | ~1 MB | Yes |
Training (200 samples, 2 labels)
| Board | Total Training Time | Per Step |
|---|---|---|
| Arduino Nano V3 | ~24 ms | ~120 us |
| XIAO SAMD21 | ~3.4 ms | ~17 us |
| ESP32-S3 | ~1.7 ms | ~8.5 us |
Full Loop Time (inference + 2x I2C read + OLED refresh + serial TX)
| Board | Inference | I2C Reads | OLED Update | Serial TX | Total | Loop Hz |
|---|---|---|---|---|---|---|
| Arduino Nano V3 | 115 us | 1,000 us | 20,000 us | 10,400 us | 31.5 ms | 32 Hz |
| XIAO SAMD21 | 25.6 us | 400 us | 8,000 us | 10,400 us | 18.8 ms | 53 Hz |
| ESP32-S3 | 4.2 us | 300 us | 5,000 us | 10,400 us | 15.7 ms | 64 Hz |
Note: I2C sensor reads and OLED refresh dominate the loop time, not inference. GenesisNode inference is a tiny fraction of each cycle. On WiFi boards, MQTT publish replaces serial TX and runs asynchronously on the second core.