Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Narrative AI Edge — Cloud-Connected IoT Edge Evaluation Kit

Reference firmware for the edge tier of a cloud-connected IoT system. Sensors → preprocessing → on-device anomaly detection (Z-score TinyML) → cloud alert via a single pluggable hook. Targets Seeed XIAO ESP32-S3 and Raspberry Pi Pico 2W.

The default build is a dry run (serial output), so you can verify payload shape and bandwidth before writing any cloud code.

Demo: XIAO ESP32-S3 + MSP2807 TFT running anomaly detection

Architecture

┌──────────────────┐   I2C: BME280, MPU6050
│  Sensors         │   Sensor list defined in embedded_configs.h
└────────┬─────────┘
         ▼
┌──────────────────┐   highpass / lowpass / magnitude / delta /
│  Preprocessing   │   abs / moving_variance — chained via JSON config
└────────┬─────────┘
         ▼
┌──────────────────┐   Z-score model, learned online and persisted
│  AnomalyEngine   │   to NVS (ESP32) / EEPROM (Pico 2W).
│                  │   M-of-N voting + per-device alert cooldown.
└────────┬─────────┘
         ▼
┌──────────────────┐    ┌─────────────────────────────────────┐
│  uplinkOnAlert() │ →  │  User-implemented cloud transport   │
│  (only hook)     │    │  HTTPS, MQTT, BLE Advertise, ...    │
└──────────────────┘    │  Default: dry run (serial output)   │
                        └─────────────────────────────────────┘

Supported boards

Board TFT (MSP2807) I2C SDA / SCL
Seeed XIAO ESP32-S3 ✓ included GPIO 5 / 6
Raspberry Pi Pico 2W ✓ included GPIO 4 / 5

src/board_config.h reads the Arduino board macro and picks I2C pins automatically. TFT pin configuration requires a one-time setup — see TFT_eSPI setup.

Quick start

Prerequisites

  • Arduino IDE 2.x
  • Board packages: esp32 by Espressif Systems (3.x) and/or Raspberry Pi Pico/RP2040 by Earle F. Philhower III (4.x).
  • Libraries:
    • ArduinoJson (Benoit Blanchon) ≥ 7.0
    • TFT_eSPI (Bodmer) ≥ 2.5.44 GitHub master branch — see Troubleshooting.

Wiring — XIAO ESP32-S3 + MSP2807

Function XIAO GPIO MSP2807 pin
TFT MOSI 9 SDI
TFT MISO 8 SDO
TFT SCLK 7 SCK
TFT CS 4 CS
TFT DC 3 DC
TFT RST 2 RESET
TFT BL 1 LED
I2C SDA 5 (sensors)
I2C SCL 6 (sensors)

Wiring — Raspberry Pi Pico 2W + MSP2807

Function Pico GPIO MSP2807 pin
TFT MOSI 19 SDI
TFT MISO 16 SDO
TFT SCLK 18 SCK
TFT CS 17 CS
TFT DC 14 DC
TFT RST 15 RESET
TFT BL 20 LED
I2C SDA 4 (sensors)
I2C SCL 5 (sensors)

TFT_eSPI setup (one-time)

Copy all three files from src/tft/setups/ into the TFT_eSPI library:

src/tft/setups/EdgeAI_AutoSelect.h            →  Arduino/libraries/TFT_eSPI/User_Setups/
src/tft/setups/Setup_XIAO_ESP32S3_MSP2807.h   →  Arduino/libraries/TFT_eSPI/User_Setups/
src/tft/setups/Setup_RPI_PICO2W_ILI9341.h     →  Arduino/libraries/TFT_eSPI/User_Setups/

Then edit Arduino/libraries/TFT_eSPI/User_Setup_Select.h:

  1. Comment out #include <User_Setup.h>.
  2. Add before the closing #endif:
    #include <User_Setups/EdgeAI_AutoSelect.h>

EdgeAI_AutoSelect.h selects the correct board setup automatically at compile time.

If your hardware uses different TFT pins, edit the matching Setup_*.h and re-copy it to libraries/TFT_eSPI/User_Setups/.

Board settings (XIAO ESP32-S3)

Setting Value
USB Mode Hardware CDC and JTAG
Flash Size match your module (4MB or 8MB)
PSRAM OPI PSRAM (recommended)

Build and flash

Open narrative-aI-edge.ino, select your board and port, then Sketch → Upload.

What you'll see (dry run)

Serial Monitor at 115200 baud:

=== Narrative IoT Edge 1 ===
[Board] xiao-esp32s3
[Storage] NVS Preferences ready
[Storage] ready
[AnomalyEngine] threshold=0.62 buf=300 retrain=300 drift=0.30 mofn=7/10 online=on
[I2C] SDA=5 SCL=6
[BME280] Initialized at 0x76
[MPU6050] Initialized at 0x68 (8g, 500deg/s, 21Hz LPF)
...
[AnomalyEngine] No model at /model.json -- edge learning starts
[ML] no model -> learning phase
[Setup] Done
[CSV] time,temperature,vibration_rms,vibration_volatility,score,mofn
[CSV] 3254,27.592,0.033,0.001,
...
[AnomalyEngine] Collecting: 300 / 300
[AnomalyEngine] Model saved
[AnomalyEngine] Model ready. Inference enabled.
[AnomalyEngine] Edge model built: version=edge-302254 samples=300 threshold=0.62

Trigger an anomaly (e.g., shake the sensor):

[ALERT] score=0.640 mofn=7 type=volatility driver=vibration_rms
[Uplink/Alert] size=352B {"device_id":"node-001","device_uptime_ms":530262,"payload_version":1,"anomaly_signature":{"score":0.64036,"type":"volatility","primary_driver":"vibration_rms","duration_seconds":0,"sensor_snapshot":{"temperature":27.89,"vibration_rms":0.158052,"vibration_volatility":0.003896}},"meta":{"mofn_count":7,"model_version":"edge-302254"}}
[CSV] 530265,27.890,0.158,0.004,0.640,7  <-- ALERT

Connecting to the cloud

Implement uplinkOnAlert(...) in src/cloud_uplink.cpp to send alerts over any transport. The default implementation is a dry run that prints the payload to serial.

Tuning

Edit src/embedded_configs.hML_JSON and rebuild.

Key Default Description
ml.anomaly_threshold 0.62 Alert threshold (sigmoid of max Z-score).
ml.buffer_target 300 Samples before the first model is built.
ml.retrain_interval 300 Online-retrain cadence (samples).
ml.alert_cooldown_minutes 10 Cooldown between alerts.
ml.mofn_n / ml.mofn_m 10 / 7 M-of-N voting window and threshold.
ml.max_drift_ratio 0.3 Drift guard tolerance for online retraining.
ml.discard_saved_model false true = re-learn from scratch on every boot.
ml.online_learning true false = freeze model after initial learning.

Per-field guardrails (min_std, max_std, hard limits) and the preprocessing pipeline are in SENSORS_JSON in the same file.

Project layout

narrative-ai-edge/
├── narrative-ai-edge.ino               Main sketch
├── README.md                           This file
└── src/
    ├── board_config.h                  Per-board pin & TFT macros
    ├── embedded_configs.h              Sensor / ML config (in-source JSON)
    ├── cloud_uplink.{h,cpp}            Cloud integration boundary
    ├── logger.h                        Serial + TFT unified logging
    ├── hal/ihal_storage.h              Storage abstraction
    ├── arduino_hal/
    │   ├── hal_wdt.h                   Watchdog wrapper
    │   └── hybrid_storage.{h,cpp}      Embedded configs + NVS / EEPROM
    ├── sensors/i2c_bus.h               I2C bus init
    ├── tft/
    │   ├── tft_display.{h,cpp}         Dashboard + scroll log
    │   └── setups/                     Copy these to TFT_eSPI/User_Setups/
    │       ├── EdgeAI_AutoSelect.h     Board-dispatch wrapper
    │       ├── Setup_XIAO_ESP32S3_MSP2807.h
    │       └── Setup_RPI_PICO2W_ILI9341.h
    ├── app/
    │   └── sensor_manager.{h,cpp}      Driver list, averaging, pipeline
    └── shared/                         Portable, transport-agnostic code
        ├── serial_log.h                LOGP() macro
        ├── time_compat.h               millis() / sleep_ms() shim
        ├── ml/anomaly_engine.{h,cpp}   Z-score learner + M-of-N
        ├── pipeline/preprocessing.{h,cpp}
        └── sensors/
            ├── i2c_sensor.h
            ├── bme280_driver.{h,cpp}
            └── mpu6050_driver.{h,cpp}

Troubleshooting

Boot crash, Guru Meditation Error: StoreProhibited — TFT_eSPI 2.5.43 is incompatible with Arduino-ESP32 3.x. Install TFT_eSPI from GitHub master:

  1. Delete Arduino/libraries/TFT_eSPI. (if installed)
  2. Download master.zip from https://github.com/Bodmer/TFT_eSPI.
  3. Arduino IDE: Sketch → Include Library → Add .ZIP Library... → select the ZIP.
  4. Re-apply the TFT_eSPI setup step (copy the three files under src/tft/setups/ to User_Setups/ and add the one-line include to User_Setup_Select.h).

TFT stays blackUser_Setup_Select.h not edited, the three setup files not copied, or TFT_eSPI not the GitHub master build. Re-check the "TFT_eSPI setup" section above.

License

MIT

Contributing

  • Sensor driver: add a file under src/shared/sensors/ implementing I2CSensor.
  • Cloud uplink: reference implementations for AWS / Azure / Google Cloud.
  • Tuning recipes: per-use-case parameter sets (sensor type, environment, min_std / anomaly_threshold / M-of-N values) with notes on the reasoning.

About

Arduino firmware for edge anomaly detection using online Z-score learning (EMA + Welford). Supports XIAO ESP32-S3 and Raspberry Pi Pico 2W with MSP2807 (ILI9341) TFT display.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages