Predictive Maintenance with TinyAutomator

Equip your production lines with a predictive maintenance solution for real-time monitoring of asset conditions and performance. 📈

Predictive Maintenance with TinyAutomator

Things used in this project

Hardware components

NORVI-IIOT-AE02-V ×1

Industrial Shields 012002000200 × 1

NTC Sensor (B57301K0103A001 EPCOS) × 1

Adafruit Analog Accelerometer: ADXL335 × 1

Software apps and online services

Waylay TinyAutomator

Hand tools and fabrication machines 

Wire Stripper / Crimper, 10AWG - 20AWG Strip

Wire Stripper / Crimper, 10AWG - 20AWG Strip

Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires

Wire Stripper & Cutter, 18-10 AWG / 0.75-4mm² Capacity Wires

Motivation

🏭 Equipping machinery with a predictive maintenance solution vastly improves the Overall Equipment Effectiveness (OEE) by offsetting production line downtime costs. Heavy industrial machinery being expensive and the waiting time for delivery and replacement being long, tracking the signs of malfunction and taking preventive measures saves a lot of resources by alerting the plant manager about potential machinery failure.

🦉 Before moving on, check out our Introduction to TinyAutomator tutorial if you haven’t already, to ensure you have the stack installed on your compatible hardware.

Our Use Case 🏭

The facility in which we deployed this device is equipped with a lot of injection moulding machinery and, while most are less than 5 years old, some of them have been moulding plastic for longer than 20 years and monitoring their running parameters is critical in preventing failures, reducing the cost of replacing an entire machine, as well as reducing factory downtime.

Our machine, pictured below, is a MEGA T 4435/610 injection moulding machine from 2000 by SANDRETTO.

MEGA T 4435/610 injection moulder by Sandretto
MEGA T 4435/610 injection moulder by Sandretto
MEGA T 4435/610 injection moulder by Sandretto
MEGA T 4435/610 injection moulder by Sandretto

Hardware requirements 🧰

  • Power supply - 24V, 1.5A;
  • NTC Temperature sensors - B57301K0103A001 EPCOS;
  • Accelerometer - Adafruit ADXL335.

For developing this predictive maintenance solution, we have picked the Industrial Shields 012002000200 RPI4B-based development board, equipped with 2GB of RAM, perfectly suited for running TinyAutomator. Our development board of choice is perfectly adapted to the industrial environment, being equipped with a DIN rail mount and being 24V tolerant. It is advisable to use a quality SD card with at least 16GB of storage.

Unusual patterns in motor vibrations or temperatures above a certain level can indicate the machine is not working properly. The sensors we picked for this application are the Adafruit ADXL335 accelerometer and the B57301K0103A001 EPCOS NTC sensor. We have decided to use Analog sensors due to their increased accuracy in representing changes in physical phenomena, such as sound, temperature and position.

Regarding tools, a crimping tool, a wire stripping tool and ferrules are necessary for wiring, as industrial PLCs use screw terminal blocks for connections.

Data gathering 📊

For our use case, we had to monitor the temperature and vibration at different points on the production line.

Here is the code used by the NORVI-IIOT-AE02-V to read the temperatures and vibrations from the monitoring points indicated by the employees. These points in the past were indicators that something might go wrong but it's hard to do this manually in the long run and you can only observe in the later stage when things are starting to malfunction (the Read3NTC_ReadADXL_SendMQTT.ino file from the Github repository).

#include 
#include 
#include 
#include 
Adafruit_ADS1115 ads1;
Adafruit_ADS1115 ads2;
#include 
 
const char* ssid = "WifiSSid";
const char* password =  "WifiPwd";
const char* mqttServer = "IpAddressMqttTinyAutomator";
const int mqttPort = 1883;
#define MQTT_PUB_TEMP "test-topic"
unsigned long last_time = 0;
unsigned long timer_delay = 1000;
///
WiFiClient espClient;
PubSubClient client(espClient);
 
///Accelerometer def
float Voltage = 0.0;
int decimalPrecision = 2;
int xvalue;
int yvalue;
int zvalue;
///
 
/// Temperature mesurement
float voltageDividerR1 = 5100;         // Resistor value in R1 for voltage devider method
float BValue = 3977;                    // The B Value of the thermistor for the temperature measuring range
float R1 = 10000;                        // Thermistor resistor rating at based temperature (25 degree celcius)
float t1 = 298.15;                      /* Base temperature t1 in Kelvin (default should be at 25 degree)*/
float R21, R22, R23 ;                            /* Resistance of Thermistor (in ohm) at Measuring Temperature*/
float t21, t22, t23 ;
float NTC_Temperature_1 = 0;/* Measurement temperature t2 in Kelvin */
float NTC_Temperature_2 = 0;
float NTC_Temperature_3 = 0;
 
float a ;                               /* Use for calculation in Temperature*/
float b1, b2, b3 ;                             /* Use for calculation in Temperature*/
float c1, c2, c3 ;                             /* Use for calculation in Temperature*/
float d1, d2, d3 ;                             /* Use for calculation in Temperature*/
float e = 2.718281828 ;                 /* the value of e use for calculation in Temperature*/
 
float tempSampleRead1  = 0;               /* to read the value of a sample including currentOffset1 value*/
float tempSampleRead2  = 0;
float tempSampleRead3  = 0;
float tempSampleSum1   = 0;
float tempSampleSum2   = 0;
float tempSampleSum3   = 0;
float tempLastSample  = 0;
float tempSampleCount = 0;
float tempMean1 ;
float tempMean2 ;
float tempMean3 ;
//////////
 
///smoothing adc reads
float NormAverage = 0;
float averagex = 0;// the average
float averagey = 0;
float averagez = 0;
float averagex100 = 0;// the average
float averagey100 = 0;
float averagez100 = 0;
//smoothing temp reads
float NTCtemperature = 0;
float averageTemperature1 = 0;
float averageTemperature2 = 0;
float averageTemperature3 = 0;
///
float volts0, tm1, tm2, tm3;
int16_t ts1, ts2, ts3;
 
 
void setup() {
 ///ADC initialization
 Serial.begin(115200);
 ads1.setGain(GAIN_ONE);
 ads1.begin(0x48, &Wire);
 ads2.setGain(GAIN_ONE);
 ads2.begin(0x49, &Wire);
 
 
 char buffer1[256];
 StaticJsonDocument<300> doc;
 //JsonObject& doc = doc.createObject();
 
 doc["device"] = "ESP32";
 doc["sensorType"] = "Temperature";
 doc["value"] = 24;
 
 char JSONmessageBuffer[100];
 serializeJson(doc, Serial);
 serializeJson(doc, buffer1);
 Serial.println("Sending message to MQTT topic..");
 Serial.println(JSONmessageBuffer);
 
 if (client.publish(MQTT_PUB_TEMP, buffer1) == true) {
   Serial.println("Success sending message");
 } else {
   Serial.println("Error sending message");
 }
 
 client.loop();
 Serial.println("-------------");
 
 delay(10000);
 Wire.begin(16, 17);
 
 ///WiFi connect&MQTT setup
 WiFi.begin(ssid, password);
 Serial.println("Connecting to WIFI…");
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   Serial.print(".");
 }
 Serial.println("");
 Serial.print("IP Address: ");
 Serial.println(WiFi.localIP());
 Serial.println("After 10 seconds the first reading will be displayed");
 client.setServer(mqttServer, mqttPort);
 //
 while (!client.connected()) {
   Serial.println("Connecting to MQTT...");
 
   if (client.connect("ESP32Client"/*, mqttUser, mqttPassword */)) {
 
     Serial.println("connected");
 
   } else {
 
     Serial.print("failed with state ");
     Serial.print(client.state());
     delay(2000);
 
   }
 }
 
}
 
void loop() {
 int16_t adc0, adc1, adc2, adc3;
 if ((millis() - last_time) > timer_delay) {
   if (WiFi.status() == WL_CONNECTED) {
     ////
 
 
     for (int i = 0; i < 100; i++) {
       tm1 = ads1.readADC_SingleEnded(3);
       ts1 = map(tm1, 0, 16000, 0, 1023);
       tm2 = ads2.readADC_SingleEnded(0);
       ts2 = map(tm2, 0, 16000, 0, 1023);
       tm3 = ads2.readADC_SingleEnded(1);
       ts3 = map(tm3, 0, 16000, 0, 1023);
       //Serial.print(tm);
 
 
       // Serial.println(ts);
       tempSampleRead1 = ts1;
       tempSampleRead2 = ts2;
       tempSampleRead3 = ts3;
 
       tempSampleSum1 = tempSampleSum1 + tempSampleRead1;
       tempSampleSum2 = tempSampleSum2 + tempSampleRead2;
       tempSampleSum3 = tempSampleSum3 + tempSampleRead3;
 
       tempSampleCount = tempSampleCount + 1;                                                      /* keep counting the sample quantity*/
       tempLastSample = millis();                                                                  /* reset the time in order to repeat the loop again*/
 
 
       if (tempSampleCount == 100)                                                                        /* after 1000 sample readings taken*/
       {
         tempMean1 = tempSampleSum1 / tempSampleCount;
         tempMean2 = tempSampleSum2 / tempSampleCount;
         tempMean3 = tempSampleSum3 / tempSampleCount; /* find the average analog value from those data*/
 
         R21 = (voltageDividerR1 * tempMean1) / (1023 - tempMean1);
         R22 = (voltageDividerR1 * tempMean2) / (1023 - tempMean2);
         R23 = (voltageDividerR1 * tempMean3) / (1023 - tempMean3);
 
         a = 1 / t1;
         b1 = log10(R1 / R21);
         c1 = b1 / log10(e);
         d1 = c1 / BValue ;
         b2 = log10(R1 / R22);
         c2 = b2 / log10(e);
         d2 = c2 / BValue ;
         b3 = log10(R1 / R23);
         c3 = b3 / log10(e);
         d3 = c3 / BValue ;
 
 
         t21 = 1 / (a - d1);
         t22 = 1 / (a - d2);
         t23 = 1 / (a - d3);
 
         Serial.print(t21 - 298.15, decimalPrecision); Serial.print("  ");
         Serial.print(t22 - 298.15, decimalPrecision); Serial.print("  ");
         Serial.print(t23 - 298.15, decimalPrecision); Serial.print("  ");
         Serial.println(" °C");
 
 
         tempSampleSum1 = 0;
         tempSampleSum2 = 0;
         tempSampleSum3 = 0;                                                                          /* reset all the total analog value back to 0 for the next count */
         tempSampleCount = 0;                                                                        /* reset the total number of samples taken back to 0 for the next count*/
       }
       NTC_Temperature_1 = NTC_Temperature_1 + (t21 - 298.15);
       NTC_Temperature_2 = NTC_Temperature_1 + (t22 - 298.15);
       NTC_Temperature_3 = NTC_Temperature_1 + (t23 - 298.15);
       //// End of Read temperature and store in NTC_Temperature_1
 
       //// Read accelerometer values
       yvalue = ads1.readADC_SingleEnded(0);                              //reads values from x-pin & measures acceleration in X direction
       int y = map(xvalue, 598, 898, -100, 100);               //maps the extreme ends analog values from -100 to 100 for our understanding
 
       float yg = float(y) / (-100.00);                        //converts the mapped value into acceleration in terms of "g"
       Serial.print(yg);                                       //prints value of acceleration in X direction
       Serial.print("g   ");                                   //prints "g"
 
       xvalue = ads1.readADC_SingleEnded(1);
       int x = map(yvalue, 602, 900, -100, 100);
       float xg = float(x) / (-100.00);
       Serial.print("\t");
       Serial.print(xg);
       Serial.print("g   ");
 
       zvalue = ads1.readADC_SingleEnded(2);
       int z = map(zvalue, 622, 932, -100, 100);
       float zg = float(z) / (100.00);
       Serial.print("\t");
       Serial.print(zg);
       Serial.println("g   ");
       //// End of Read accelerometer values
       averagex100 = averagex100 + xg;
       averagey100 = averagey100 + yg;
       averagez100 = averagez100 + zg;
       delay(18000); //9000=15 min
     }
     averagex = averagex100 / 100;
     averagey = averagey100 / 100;
     averagez = averagez100 / 100;
     averageTemperature1 = NTC_Temperature_1 / 100;
     averageTemperature2 = NTC_Temperature_2 / 100;
     averageTemperature3 = NTC_Temperature_3 / 100;
     NormAverage = sqrt(averagex * averagex + averagey * averagey + averagez * averagez);
 
     ////////////////////////////////////Sending Json Via MQTT Post Req//////////////////////////////////
     char buffer1[256];
     StaticJsonDocument<300> doc;
     //JsonObject& doc = doc.createObject();
 
     doc["device"] = "ESP32_NorviIIot";
     doc[" Temperature_1"] = averageTemperature1;
     doc[" Temperature_2"] = averageTemperature2;
     doc[" Temperature_3"] = averageTemperature3;
     doc[" AverageAccelerationX"] = averagex;
     doc[" AverageAccelerationY"] = averagey;
     doc[" AverageAccelerationZ"] = averagez;
     doc[" Vibration"] = NormAverage;
     averagex100 = 0;
     averagey100 = 0;
     averagez100 = 0;
     NTC_Temperature_1 = 0;
     NTC_Temperature_2 = 0;
     NTC_Temperature_3 = 0;
     char JSONmessageBuffer[100];
     serializeJson(doc, Serial);
     serializeJson(doc, buffer1);
     Serial.println("Sending message to MQTT topic..");
     Serial.println(JSONmessageBuffer);
 
     if (client.publish(MQTT_PUB_TEMP, buffer1) == true) {
       Serial.println("Success sending message");
     } else {
       Serial.println("Error sending message");
     }
 
     client.loop();
     Serial.println("-------------");
     delay(10000);
   }
   else {
     Serial.println("WiFi is Disconnected!");
   }
   last_time = millis();
 }
}

The code after reading the temperatures and vibrations sends them through MQTT to the TinyAutomator install. Be careful to replace the WiFi SSID (ssid), the password (password), and the IP address of your device running TinyAutomator (mqttServer).

The cool thing about TinyAutomator is that once you send data over MQTT the resources are created automatically and they are stored locally on the devices via the InfluxDB that is included in the Docker install.

Besides monitoring the temperatures and vibrations automatically in TinyAutomator, it’s a good idea to also take note of use time and products being manufactured on the machine since they will impact the data from the sensors. This information will help you create a model that can be applied automatically by labelling these intervals accordingly. The most important parts are what you consider the machine running in good parameters. Any different activity will be interpreted as a possible issue in the functioning of the machine.

Creating an ML model using Edge Impulse 🧠

Now that we’ve gathered some data from the sensors, we can build, train, and deploy a Machine Learning model. For this task, we recommend using Edge Impulse that helps you create an ML model from a dataset in a visual way using low-code programming. We also recommend reading this tutorial to get a proper understanding of all the available features on Edge Impulse.

Start by registering on Edge Impulse and creating a new project. We’ve named ours Predictive-Maintenance.

We'll need to push some training data from Waylay TinyAutomator to Edge Impulse. For the time being, you can export the data in the JSON format from Waylay TinyAutomator using the following command:

curl -i --user apiKey:apiSecret "http://standalone.waylay.io:9000/messages/test-topic"

(the apiKey and apiSecret are default values used locally directly on the Tiny Automator install, if you want to use it remotely we recommend changing it to some strong credentials).

However, the data needs to be formatted according to the Edge Impulse Data Acquisition Format so your JSON should look something like this:

{
   "protected": {
       "ver": "v1",
       "alg": "HS256",
       "iat": 1610601116
   },
   "signature": "generated_signature_id",
   "payload": {
       "device_name": "Industrial Shields PLC",
       "device_type": "012002000200",
       "interval_ms": 2,
       "sensors": [
           {"name": "Z_Accel_N1", "units": "m/s2"},
           {"name": "Temperature3_N3", "units": "Cel"},
           {"name": "X_Accel_N1", "units": "m/s2"},
           {"name": "AvgAccY", "units": "m/s2"},
           {"name": "Temperature1_N1", "units": "Cel"},
           {"name": "NormAcc_N1", "units": "m/s2"},
           {"name": "Y_Accel_N1", "units": "m/s2"},
           {"name": "Temperature2_N2", "units": "Cel"}
       ],
       "values": [
           [0.84, 24.58, -0.06, 0.17, 24.34, 0.86, 0.17, 24.58],
           [0.90, 25.58, -0.05, 0.13, 22.34, 0.26, 0.13, 25.58],
           [0.75, 24.56, -0.03, 0.14, 24.35, 0.16, 0.16, 24.78],
           [0.66, 24.78, -0.01, 0.18, 24.67, 0.88, 0.13, 24.68],
           [0.87, 24.98, -0.04, 0.17, 24.74, 0.89, 0.12, 24.89]
       ]
   }
}

Then, we’ll need to upload the JSON data exported from TinyAutomator to Edge Impulse. Go to Data acquisition -> Let’s collect some data -> Upload data (Go to the uploader). Select the JSON file and click Begin Upload. Now you should be able to see the device and the data:

Once you've added enough data samples, you'll need to label them (e.g. bad/good) and split them equally between the Training data and Testing data categories.

Now you can move on to the next step: designing an impulse.

After tuning the ML model to your needs, generating the features, and training the neural network, we can deploy the model to the Industrial Shields PLC.

You'll need to set up Install the Edge Impulse for Linux CLI on the Industrial Shields RPI-based PLC by following this tutorial (Step 2: Installing dependencies):

$ curl -sL https://deb.nodesource.com/setup_12.x | sudo bash -
sudo apt install -y gcc g++ make build-essential nodejs sox gstreamer1.0-tools $ gstreamer1.0-plugins-good gstreamer1.0-plugins-base gstreamer1.0-plugins-base-apps
$ npm config set user root && sudo npm install edge-impulse-linux -g --unsafe-perm

We will also need to install the Linux Python SDK:

$ sudo apt-get install libatlas-base-dev libportaudio0 libportaudio2 libportaudiocpp0 portaudio19-dev
$ pip3 install edge_impulse_linux -i https://pypi.python.org/simple

You can download the model file either via command line:

$ edge-impulse-linux-runner --download modelfile.eim

or directly from the Edge Impulse interface:

And you can use this example to run the model on the device:

$ python3 classify.py predictive_maintenance.eim features.txt
MODEL: /home/waylay/predictive_maintenance.eim
Loaded runner for "Alexandra / Predictive-Maintenance"
classification:
{'classification': {'bad': 0.35807016491889954, 'good': 0.4811112582683563, 'idle01': 0.16081854701042175}}
timing:
 
{'anomaly': 0, 'classification': 0, 'dsp': 0, 'json': 0, 'stdin': 2}

As you can see the model detected the machine as not being idle and with a higher percentage of working in a better state than a dangerous one. Of course as with any Machine Learning the more you train it and the more data you pour in and label the better and more accurate the results will be.

What’s next? 🚀

A Predictive maintenance ML model gets more accurate if you have more data samples that are labelled based on your observations and other indirect data like running times, parts produced and so on (factors that can influence the vibration and temperature readings). While this may sound cumbersome the benefits in the long run far outweigh the initial process and save you from downtime and thus delays in delivering your goods to clients.

We have other tutorials from which you can learn to use TinyAutomator for industrial use-cases:

If you need help in deploying this solution or building something similar please contact Waylay.io for the low-code IoT Solution or Zalmotek.com for IoT-enabled hardware prototype.

Code
Github Repository

Zalmotek / Predictive_maintenance_with_tiny_automator