Whenever we think of Artificial Intelligence and Machine Learning; What comes to our mind? Maybe Powerful Computers, Huge Servers/Data, and a lot of Computation Power. But Edge AI uses very Low Power devices that can be operated with a battery, working for years with just a single charge.
Conventional maintenance techniques in the industry require a technician to visit the site and check for any failures. This is done periodically. But what if the machines are placed in a remote environment(sky-high power lines or deep under the ground down machinery)? In those cases, Conditioning monitoring with IoT would come into play. Tiny ML at the edge acts as icing on Top when you don't have to send each and every data to the cloud for analysis and waste power. You can perform inference with a lightweight ML model on a low-power microcontroller and predict whether any fault. The combination of Sensors and Machine Learning helps create an early warning system.
WizFi360-EVB-PicoWIZnet WizFi360-EVB-Pico is based on Raspberry Pi RP2040 and adds Wi-Fi connectivity using WizFi360. It is pin compatible with the Raspberry Pi Pico board and can be used for IoT Solution development. It would be a good choice to develop my prototype - Condition Monitoring Setup with an Agri Motor Pump.
Prototype for DemonstrationThis device does not require any intrusive mechanism to offer Condition monitoring. It can be attached to the external surface and you are good to go.
WorkingThis project is an Accelerometer-based Condition Monitoring system that predicts the health of the Machine with the help of vibrational data. To accomplish this, I chose to go with Analog Devices EVAL-ADXL355Z Sensor.
The Project had 3 Phases:
1. Collection of Dataset
2. Live Inference
3. Connection with Thingspeak
Collection of DatasetWiznet WizFi360-EVB-Pico does support Arduino IDE but I chose to go with PlatformIO. In order to interface with EVAL-ADXL355Z I opted for the NO-OS drivers I made the connections with the sensor as shown in the diagram below. Data collection code: ADXL355 is a separate firmware to fetch the accelerometer values of the X, Y, and Z axes. It pushes these values onto the serial terminal, which is then picked up by the Edge Impulse Data-forwarder tool. The data-acquisition frequency was 85Hz.
I have collected data for 4 Scenarios:
1. Normal-Flow: Proper water supply without air bubbles.
2. Bubbly_Airlflow: Presence of bubbles in water flow.
3. Air_Flow: Presence of no water. The motor should stop.
4. Off Condition: When the motor is switched off.
/*************************************
***** Data Aquisition Code *****
*********** Platform IO ************
*************************************/
#include <Arduino.h>
#include "stdlib.h"
extern "C" {
#include "adxl355.h"
#include "no_os_spi.h"
#include "pico_spi.h"
#define SPI_DEVICE_ID 1
#define SPI_BAUDRATE 1000000
//#define SPI_CS SPI1_CS_GP13
//#define SPI_OPS &pico_spi_ops
//#define SPI_EXTRA &adxl355_spi_extra_ip
// Read single accel data
struct adxl355_frac_repr x;
struct adxl355_frac_repr y;
struct adxl355_frac_repr z;
struct pico_spi_init_param adxl355_spi_extra_ip = {
.spi_tx_pin = SPI1_TX_GP11,
.spi_rx_pin = SPI1_RX_GP12,
.spi_sck_pin = SPI1_SCK_GP10,
.spi_cs_pin = SPI1_CS_GP13
};
static struct adxl355_dev *adxl355;
// Particular SPI configuration
// static struct no_os_spi_init_param adxl355_spi_init = {
// .max_speed_hz = 1000000,
// .mode = 0,
// .bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST,
// .extra = &adxl355_spi_extra_ip
// };
no_os_spi_init_param adxl_spi_init= {
.device_id = SPI_DEVICE_ID,
.max_speed_hz = SPI_BAUDRATE,
.chip_select = SPI1_CS_GP13,
.mode = NO_OS_SPI_MODE_0,
.bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST,
.platform_ops = &pico_spi_ops,
.extra = &adxl355_spi_extra_ip
};
struct adxl355_init_param init_data_adxl355 = {
.comm_init = {
.spi_init = adxl_spi_init },
.comm_type = ADXL355_SPI_COMM
};
}
void error(void){
printf("Initialization Error \r\n");
while(1);
}
void setup() {
int ret;
Serial.begin(256000);
ret = adxl355_init(&adxl355, init_data_adxl355);
if (ret < 0)
error();
ret = adxl355_set_range(adxl355, ADXL355_RANGE_8G);
if (ret < 0)
error();
//ret = adxl355_soft_reset(adxl355);
//sleep_ms(250);
if (ret < 0)
error();
// ret = adxl355_set_odr_lpf(adxl355, ADXL355_ODR_4000HZ);
// if (ret < 0)
// error();
ret = adxl355_set_op_mode(adxl355, ADXL355_MEAS_TEMP_OFF_DRDY_OFF);
if (ret < 0)
error();
}
void loop() {
// put your setup code here, to run once:
int ret;
ret = adxl355_get_xyz(adxl355, &x, &y, &z);
if (ret < 0)
error();
printf("%lld \t",x.integer);
printf("%lld \t",y.integer);
printf("%lld \n",z.integer);
x.fractional = (x.fractional < 0)?(-x.fractional):x.fractional;
y.fractional = (y.fractional < 0)?(-y.fractional):y.fractional;
z.fractional = (z.fractional < 0)?(-z.fractional):z.fractional;
Serial.print(x.integer);
Serial.print(".");
Serial.print(x.fractional);
Serial.print("\t");
Serial.print(y.integer);
Serial.print(".");
Serial.print(y.fractional);
Serial.print("\t");
Serial.print(z.integer);
Serial.print(".");
Serial.println(z.fractional);
delay(10);
}
After the collection of the dataset, I created the following Edge Impulse model.
Impulse input has a data window size to be 3seconds and a window increase to be 250ms. Pre-processing was done with the spectral Analysis tool. And for classification, I chose Keras based model.
The model accuracy turned out to be 95% while performing live classification and model testing.
Then I used EON Tuner to have a smaller and lightweight ML model. The generated Arduino library had my ML model and Edge impulse SDK which I ported to my project.
Inference Code and EdgeImpulse Library PortingGenerate the Arduino Library for the ML Model with a quantized int8 type classifier. Include this .zip library manually in Platform IO(Delete the Examples folder within).
#include <Arduino.h>
#include "WizFi360_Sobhit_inferencing.h"
#include "stdlib.h"
#define debug_nn true
extern "C" {
#include "adxl355.h"
#include "no_os_spi.h"
#include "pico_spi.h"
#define SPI_DEVICE_ID 1
#define SPI_BAUDRATE 1000000
//#define SPI_CS SPI1_CS_GP13
//#define SPI_OPS &pico_spi_ops
//#define SPI_EXTRA &adxl355_spi_extra_ip
// Read single accel data
struct adxl355_frac_repr x;
struct adxl355_frac_repr y;
struct adxl355_frac_repr z;
struct pico_spi_init_param adxl355_spi_extra_ip = {
.spi_tx_pin = SPI1_TX_GP11,
.spi_rx_pin = SPI1_RX_GP12,
.spi_sck_pin = SPI1_SCK_GP10,
.spi_cs_pin = SPI1_CS_GP13
};
static struct adxl355_dev *adxl355;
// Particular SPI configuration
// static struct no_os_spi_init_param adxl355_spi_init = {
// .max_speed_hz = 1000000,
// .mode = 0,
// .bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST,
// .extra = &adxl355_spi_extra_ip
// };
no_os_spi_init_param adxl_spi_init= {
.device_id = SPI_DEVICE_ID,
.max_speed_hz = SPI_BAUDRATE,
.chip_select = SPI1_CS_GP13,
.mode = NO_OS_SPI_MODE_0,
.bit_order = NO_OS_SPI_BIT_ORDER_MSB_FIRST,
.platform_ops = &pico_spi_ops,
.extra = &adxl355_spi_extra_ip
};
struct adxl355_init_param init_data_adxl355 = {
.comm_init = {
.spi_init = adxl_spi_init },
.comm_type = ADXL355_SPI_COMM
};
}
void error(void){
printf("Initialization Error \r\n");
while(1);
}
void setup() {
int ret;
ret = adxl355_init(&adxl355, init_data_adxl355);
if (ret < 0)
error();
ret = adxl355_set_range(adxl355, ADXL355_RANGE_8G);
if (ret < 0)
error();
//ret = adxl355_soft_reset(adxl355);
//sleep_ms(250);
if (ret < 0)
error();
// ret = adxl355_set_odr_lpf(adxl355, ADXL355_ODR_4000HZ);
// if (ret < 0)
// error();
ret = adxl355_set_op_mode(adxl355, ADXL355_MEAS_TEMP_OFF_DRDY_OFF);
if (ret < 0)
error();
if (EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME != 3) {
ei_printf("ERR: EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME should be equal to 3 (the 3 sensor axes)\n");
return;
}
}
void loop() {
// put your setup code here, to run once:
int ret;
ei_printf("\nStarting inferencing in 2 seconds...\n");
delay(2000);
ei_printf("Sampling...\n");
// Allocate a buffer here for the values we'll read from the IMU
float buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE] = { 0 };
for (size_t ix = 0; ix < EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE; ix += 3) {
// Determine the next tick (and then sleep later)
uint64_t next_tick = micros() + (EI_CLASSIFIER_INTERVAL_MS * 1000);
ret = adxl355_get_xyz(adxl355, &x, &y, &z);
if (ret < 0)
error();
x.fractional = (x.fractional < 0)?(-x.fractional):x.fractional;
y.fractional = (y.fractional < 0)?(-y.fractional):y.fractional;
z.fractional = (z.fractional < 0)?(-z.fractional):z.fractional;
buffer[ix] = (x.integer>0)?(x.integer + (x.fractional >> 28)/10000):(x.integer - (x.fractional >> 28)/10000);
buffer[ix + 1] = (y.integer>0)?(y.integer + (y.fractional >> 28)/10000):(y.integer - (y.fractional >> 28)/10000);
buffer[ix + 2] = (z.integer>0)?(z.integer + (z.fractional >> 28)/10000):(z.integer - (z.fractional >> 28)/10000);
delayMicroseconds(next_tick - micros());
}
// Turn the raw buffer in a signal which we can the classify
signal_t signal;
int err = numpy::signal_from_buffer(buffer, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
if (err != 0) {
ei_printf("Failed to create signal from buffer (%d)\n", err);
return;
}
// Run the classifier
ei_impulse_result_t result = { 0 };
err = run_classifier(&signal, &result, debug_nn);
if (err != EI_IMPULSE_OK) {
ei_printf("ERR: Failed to run classifier (%d)\n", err);
return;
}
// print the 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");
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 EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
#endif
}
Connection with ThingspeakThere is an example code in the WizFi360 library, it can be used to connect to your Thingspeak server and monitor the data.
Comments