Roni Bandini
Created April 9, 2022 © MIT

Power outages anticipation device ready for Maker Faire Rome

Edenoff, a TinyML Arduino device was modified for secure and interactive demos at Maker Faire Rome 2022

IntermediateFull instructions provided4 hours14
Power outages anticipation device ready for Maker Faire Rome

Things used in this project

Hardware components

Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
×1
DFRobot Digital Buzzer
×1
DFRobot FF Jumper wires
×1
Gravity:Analog Rotation Potentiometer Sensor V1 For Arduino
DFRobot Gravity:Analog Rotation Potentiometer Sensor V1 For Arduino
×1
Gravity I2C OLED-2864 Display
DFRobot Gravity I2C OLED-2864 Display
×1
DFRobot button light buttons
×1

Software apps and online services

Arduino IDE
Arduino IDE
Fusion 360
Autodesk Fusion 360
Edge Impulse Studio
Edge Impulse Studio

Story

Read more

Code

Edenoff demo with potentiometer and Oled screen

C/C++
Edenoff demo version
/* EdenOff 
 * Power outages prediction with Machine Learning
 * Using Edge Impulse Platform
 * Maker Faire Rome 2022 Demo version 
 * Roni Bandini, Argentina, April 2022, update September 2022
 * @RoniBandini
 */

#include <VoltageVariation_inferencing.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "DHT.h"

#define SCREEN_WIDTH 128  
#define SCREEN_HEIGHT 32  
#define OLED_RESET     4  
#define SCREEN_ADDRESS 0x3C  
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


#define imageWidthLogo 128
#define imageHeightLogo 32

# define pinBuzzer 9
# define pinLed 10
# define DHTPIN 11
# define pinButton 12 

# define DHTTYPE DHT11    

const unsigned char logo [] PROGMEM = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 
  0xff, 0xff, 0xe0, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x08, 0x20, 0x00, 0x00, 0x07, 0xff, 0xff, 
  0xff, 0xff, 0xe0, 0x44, 0x08, 0x00, 0x10, 0x02, 0x40, 0x00, 0x00, 0x08, 0x88, 0x07, 0xff, 0xff, 
  0xff, 0xff, 0xe8, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x07, 0xff, 0xff, 
  0xff, 0xff, 0xf7, 0xbf, 0xef, 0xee, 0xdf, 0xee, 0xff, 0xff, 0xfb, 0xee, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x7f, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x01, 0x00, 0x7f, 0xc0, 0x00, 0x0f, 0xe0, 0x7f, 0x80, 0xf0, 0x0f, 0xff, 0xff, 
  0xff, 0xff, 0xfc, 0x01, 0x00, 0x3f, 0x00, 0x40, 0x07, 0x80, 0x1e, 0x00, 0xe0, 0x0f, 0xff, 0xff, 
  0xff, 0xff, 0xf8, 0x00, 0x00, 0x1f, 0x00, 0x40, 0x03, 0xc0, 0x1e, 0x00, 0xe0, 0x0f, 0xff, 0xff, 
  0xff, 0xff, 0xf8, 0x4b, 0x12, 0x0c, 0x15, 0x47, 0x43, 0x02, 0x0c, 0x14, 0x82, 0xaf, 0xff, 0xff, 
  0xff, 0xff, 0xf0, 0x7e, 0x1f, 0x0c, 0x1f, 0xc7, 0xe1, 0x0f, 0x00, 0x7f, 0x83, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xf0, 0xcb, 0x1f, 0x8c, 0x6a, 0x87, 0xe3, 0x1f, 0x80, 0x95, 0x8d, 0x5f, 0xff, 0xff, 
  0xff, 0xff, 0xf1, 0x01, 0x1f, 0x84, 0x40, 0x47, 0xe1, 0x1f, 0xc0, 0xc1, 0x88, 0x1f, 0xff, 0xff, 
  0xff, 0xff, 0xf1, 0x01, 0x1f, 0x84, 0x40, 0xc7, 0xe1, 0x1f, 0x80, 0x81, 0x88, 0x0f, 0xff, 0xff, 
  0xff, 0xff, 0xf1, 0x81, 0x1f, 0x84, 0x40, 0x47, 0xe1, 0x1f, 0xc0, 0x81, 0x08, 0x1f, 0xff, 0xff, 
  0xff, 0xff, 0xf0, 0x5b, 0x1f, 0x8c, 0x35, 0x47, 0xe1, 0x0f, 0x80, 0xe9, 0x8d, 0xbf, 0xff, 0xff, 
  0xff, 0xff, 0xf0, 0xfe, 0x1f, 0x0c, 0x1f, 0x87, 0xf1, 0x0f, 0x88, 0xff, 0x8f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xf8, 0x15, 0x14, 0x0e, 0x15, 0xc7, 0xe3, 0x80, 0x08, 0xff, 0x8f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xf8, 0x01, 0x10, 0x1f, 0x00, 0x47, 0xf1, 0xc0, 0x08, 0xff, 0x8f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xfe, 0x01, 0x10, 0x3f, 0x00, 0x07, 0xe1, 0xc4, 0x38, 0xff, 0x8f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0x81, 0x10, 0xff, 0xc0, 0x47, 0xe1, 0xf0, 0x70, 0xff, 0x0f, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xe8, 0xa9, 0x2a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0x95, 0x52, 0x57, 0xff, 0xff, 
  0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 
  0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 
  0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 
  0xff, 0xff, 0xf2, 0x48, 0x40, 0x41, 0x12, 0x48, 0x40, 0x10, 0x84, 0x92, 0x44, 0xa7, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};


// Temp
DHT dht(DHTPIN, DHTTYPE);

float old_temp = 0;
float old_hum = 0;

int failFlag=0;
float failureInference=0;

// Axis array
float features[3];

// Display
String line1="";
String line2="";
String line3="";
String line4="";
int myX=20;
int myY=0;

int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) {
    memcpy(out_ptr, features + offset, length * sizeof(float));
    return 0;
}


float voltage=0;
float temperature=0;
float avglatest5=0;
float sumVoltage=0;
int   myCounter=0;
int inferenceCounter=0;
float threesold=0.85;
int testFail=0;
int iterationsForAvg=5;
int delayIteration=3000;

// buzzer beep
void myBeep(){     
     tone(pinBuzzer, 349, 500);
     delay(150);                        
     tone(pinBuzzer, 200, 500);
     delay(150); 
     tone(pinBuzzer, 150, 500);
     delay(500);   
     noTone(pinBuzzer);
}

void printLines(){
  
  // horizontal line
  display.drawLine(1, 20, 128, 20,SSD1306_WHITE);
  
  // vertical line
  display.drawLine(1, 0, 1, 32,SSD1306_WHITE);
  
  // horizontal scales
  for (int i=1; i <= 128; i=i+25){
    display.drawLine(i, 17, i, 20,SSD1306_WHITE);
   }
  
   // vertical scales
  for (int i=1; i <= 20; i=i+5){
    display.drawLine(1, i, 3, i,SSD1306_WHITE);
   }
}
  
void updateScreen(){

      display.clearDisplay();
      display.setTextSize(1);    
      display.setCursor(0, 0);        
      display.setTextColor(SSD1306_WHITE); 
      display.println(line1);
      display.println(line2); 
      display.println(line3); 
      display.println(line4); 
      display.display();
}
  
void setup()
{

    pinMode(pinBuzzer, OUTPUT);  
    pinMode(pinLed, OUTPUT);  
    pinMode(pinButton, INPUT); 
    
    dht.begin();
    
    Serial.begin(115200);
    Serial.println("EdenOff v 2.0");
    Serial.println("TinyML via Edge Impulse");
    Serial.println("Roni Bandini, September 2022");
    Serial.println("@RoniBandini");
    Serial.println("------------------------------------");

     if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 allocation failed"));
        for(;;); // Don't proceed, loop forever
    }

      display.display();
      delay(2000); 
      display.clearDisplay();   
      display.drawBitmap(0, 0,  logo, imageWidthLogo, imageHeightLogo, 1); 
      display.display();   
      delay(4000);
    
      display.clearDisplay();
      display.setTextSize(1);            
      display.setTextColor(SSD1306_WHITE); 
      display.println("Edenoff 2.0");
      display.println("Arduino & TinyML"); 
      display.println("@RoniBandini 9/22"); 
      display.println("Maker Faire Rome");
      display.display();
      delay(3000); 

      display.clearDisplay();
  
}


void loop()

{

  int butValue = digitalRead(pinButton);

  // read temp
  temperature=dht.readTemperature();  
  
  // read AC
    
  int potValue = analogRead(A1);
  
  // map pot to voltage
  voltage=map(potValue, 0, 1024, 260, 180);

    //Serial.println("Pot voltage: ");
    //Serial.println(voltage);

  // map led intensity
  int ledValue=map(voltage, 199, 260, 0, 255);

  //Serial.println("Led value: ");
  //Serial.println(ledValue);
    
  // write to led
  analogWrite(pinLed,ledValue);
         
  if (myCounter==iterationsForAvg+1){                                   

        inferenceCounter++;
        
        if (sumVoltage>0) {
          avglatest5=(sumVoltage)/iterationsForAvg;
        }

        ei_printf("Inference #: ");
        Serial.println(inferenceCounter);
        
        ei_printf("Latest AC: ");
        Serial.println(voltage);

        ei_printf("AVG 5 AC: ");
        Serial.println(avglatest5);

        ei_printf("Temperature: ");
        Serial.println(temperature);

        features[0] = voltage;
        features[1] = temperature;
        features[2] = avglatest5;

        if (sizeof(features) / sizeof(float) != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
            ei_printf("The size of your 'features' array is not correct. Expected %lu items, but had %lu\n",
                EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, sizeof(features) / sizeof(float));
            delay(1000);
            return;
        }
    
        ei_impulse_result_t result = { 0 };
    
        // the features are stored into flash, and we don't want to load everything into RAM
        signal_t features_signal;
        features_signal.total_length = sizeof(features) / sizeof(features[0]);
        features_signal.get_data = &raw_feature_get_data;
    
        // invoke the impulse
        EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, false /* debug */);
        ei_printf("run_classifier returned: %d\n", res);
    
        if (res != 0) return;
    
        // print predictions
        ei_printf("Predictions ");
        ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
            result.timing.dsp, result.timing.classification, result.timing.anomaly);
        ei_printf(": \n");
        ei_printf("[");
        for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
            ei_printf("%.5f", result.classification[ix].value);
          #if EI_CLASSIFIER_HAS_ANOMALY == 1
                  ei_printf(", ");
          #else
                  if (ix != EI_CLASSIFIER_LABEL_COUNT - 1) {
                      ei_printf(", ");
                  }
          #endif
              }
          #if EI_CLASSIFIER_HAS_ANOMALY == 1
              ei_printf("%.3f", result.anomaly);
          #endif
              ei_printf("]\n");
    
          // human-readable predictions
          for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
              ei_printf("    %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);

            if (result.classification[ix].label=="failure"){
              failureInference=result.classification[ix].value;  
             }            


            if (float(result.classification[ix].value)>threesold and result.classification[ix].label=="failure" and butValue==1)
            {              
              // power outage detected
              myBeep(); 
              display.clearDisplay();
              line1="Outage coming";
              line2="Inference "+String(result.classification[ix].value)+"%";
              line3=String(voltage)+" V";
              line4=String(temperature)+" Celsius";              
              updateScreen();
              delay(5000);
              failFlag=1;                                          
            }
                       
          }
          
          #if EI_CLASSIFIER_HAS_ANOMALY == 1
              ei_printf("    anomaly score: %.3f\n", result.anomaly);
          #endif


        if (failFlag==0 and butValue==1){

          // no outage, just print avg readings  
          display.clearDisplay();      
          line1="Regular service";
          line2="Inference "+String(failureInference)+" %";  
          line3=String(voltage)+" AC "+ String(avglatest5)+" Avg";
          line4=String (temperature)+" Celsius";              
          updateScreen();      
          delay(5000);
                                 
        
        }
       
          // reset calculations
          avglatest5=0;
          sumVoltage=0;
          myCounter=0;
          failFlag=0; 
          myX=10;   
          display.clearDisplay();   
        
     }
     else
     {

        ei_printf("Counter: ");
        Serial.println(myCounter);
  
        // iterations did not reach limit
        
        //Serial.print("Voltage: ");
        //Serial.println(voltage);   
        
        sumVoltage=sumVoltage+voltage;               
        
        printLines();
        
        // map voltage 1 to 32
        myY=map(voltage, 180, 260, 10, 32);
        if (myY<1) {myY=1;}        
        myY=32-myY; // inverse scale
        //Serial.print("Mapped Voltage: ");
        //Serial.println(myY); 

        // print voltage circle
        display.fillCircle(myX, myY, 2,SSD1306_WHITE);
        
        // print temp
        display.setCursor(myX, 24);
        int intTemp=int(temperature);
        display.println(intTemp);
          
        display.display();

        // increase x
        myX=myX+20;
        
        // check limit
        if (myX>120) {
          Serial.print("myX>120 clearing display, reseting counter");
          myX=20;
          myCounter=0;
          display.clearDisplay();
        }

        
      }
           
    delay(delayIteration);
    
    myCounter=myCounter+1;
    
}

void ei_printf(const char *format, ...) {
    static char print_buf[1024] = { 0 };

    va_list args;
    va_start(args, format);
    int r = vsnprintf(print_buf, sizeof(print_buf), format, args);
    va_end(args);

    if (r > 0) {
        Serial.write(print_buf);
    }
}

Credits

Roni Bandini

Roni Bandini

45 projects • 118 followers
Maker, writer and musician. Speaker at Maker Faire, TedX, Hope and Nerdearla. Former entrepeneur.

Comments