Edenoff, a TinyML Arduino device was modified for secure and interactive demos at Maker Faire Rome 2022
Things used in this project
Hardware components
Software apps and online services
Autodesk Fusion 360: https://www.hackster.io/autodesk/products/fusion-360?ref=project-277e05
Story
Just shut off the light, Ooh, let the good times roll, child (Shut off the light, Betty Davis, 1975)
Simulated AC voltage, external temp sensor and Oled info screen
Power outages are not unlikely in Argentina and other regions of Latin America. On one hand, the gove rnment says it is due to lack of private infrastructure investment. On the other, electricity distributors argues non discriminated subsidized rates and regulations. One case or another, private companies' production and even equipment – not so easy to replace and import in Argentina - suffers.
This scenario was the starting point for a project named EdenOff (after Edenor, one of two power distributor in Buenos Aires)
Original version
Device fits into a power outlet
The original version was designed to fit a power outlet with a Zmpt101b voltage sensor, but that doesn't sound safe enough for a crowded Maker Faire.
On the other hand, Italy is not Argentina and the Faire will be over way before sensing significant AC variations.
Wall mount
So, I've decided to replace the Zmpt101b by a potentiometer and show the "AC intensity" with a Led.
int potValue = analogRead(A1);
// map pot to voltage
voltage=map(potValue, 0, 1024, 260, 180);
// map led intensity
int ledValue=map(voltage, 199, 260, 0, 255);
// write to led
analogWrite(pinLed,ledValue);
The internal temperature sensor was replaced by an external one, so a heat source will modify the data provided to the model.
External Temp Sensor
#include "DHT.h"
# define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// setup
dht.begin();
// loop
temperature=dht.readTemperature();
To make things more interactive, I've also added an Oled screen, so every AC reading along with temperature is displayed in X/Y chart
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);
}
}
// map voltage 1 to 32
myY=map(voltage, 180, 260, 10, 32);
if (myY<1) {myY=1;}
myY=32-myY; // inverse scale
// 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();
}
Everything seems ready. Dots are AC readings, numbers are temperature in Celsius
Visiting Maker Faire Rome this week?
Come play with Edenoff at Stand C.38 (pav. C). L 1.19 I will have ready other interesting devices as well. Electronic parts provided by DFRobot.
Edenoff Maker Faire Rome 2022 Flyer
Demo video (english CC caption available)
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);
}
}
The article was first published in hackster, April 9, 2022
cr: https://www.hackster.io/roni-bandini/power-outages-anticipation-device-ready-for-maker-faire-rome-277e05
author: Roni Bandini