509 lines
15 KiB
C
509 lines
15 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <math.h>
|
|
|
|
#include <dataAcquisition.h>
|
|
|
|
int outlierCount;
|
|
|
|
// Variable definition
|
|
static float **readings;
|
|
static int sensorsNumber;
|
|
static int slidingWindowSize;
|
|
|
|
/**
|
|
* @brief Sets the number of sensors.
|
|
*
|
|
* This function sets the global variable `sensorsNumber` to the specified value.
|
|
*
|
|
* @param number The number of sensors to set.
|
|
*/
|
|
static void setSensorsNumber(int number)
|
|
{
|
|
sensorsNumber = number;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the size of the sliding window.
|
|
*
|
|
* This function sets the size of the sliding window used for data acquisition.
|
|
*
|
|
* @param size The desired size of the sliding window.
|
|
*/
|
|
static void setSlidingWindowSize(int size)
|
|
{
|
|
slidingWindowSize = size;
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes the sensor readings array.
|
|
*
|
|
* This function allocates memory for a 2D array to store sensor readings.
|
|
* Each sensor will have a sliding window of readings.
|
|
*
|
|
* @param numSensors The number of sensors.
|
|
* @param windowSize The size of the sliding window for each sensor.
|
|
*
|
|
* The function performs the following steps:
|
|
* 1. Allocates memory for the array of sensor readings.
|
|
* 2. Allocates memory for each sensor's sliding window of readings.
|
|
* 3. Sets the number of sensors and the sliding window size using private setter functions.
|
|
*
|
|
* If memory allocation fails at any point, the function prints an error message and exits the program.
|
|
*/
|
|
void initializeReadings(int numSensors, float deltaTime)
|
|
{
|
|
|
|
int windowSize = (int)(1000 / deltaTime); // Standard case of 1 second of acquisition
|
|
|
|
// Allocate memory for the array of sensor readings
|
|
readings = (float **)malloc(numSensors * sizeof(float *));
|
|
|
|
// Check if memory allocation was successful
|
|
if (readings == NULL)
|
|
{
|
|
perror("Failed to allocate memory for readings");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Allocate memory for each sensor's sliding window of readings
|
|
for (int i = 0; i < numSensors; i++)
|
|
{
|
|
readings[i] = (float *)calloc(windowSize, sizeof(float));
|
|
if (readings[i] == NULL)
|
|
{
|
|
perror("Failed to allocate memory for sensor readings");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
// Calling the private setter functions
|
|
setSensorsNumber(numSensors);
|
|
setSlidingWindowSize(windowSize);
|
|
}
|
|
|
|
/**
|
|
* @brief Frees the memory allocated for sensor readings.
|
|
*
|
|
* This function iterates through the array of sensor readings and frees the memory
|
|
* allocated for each individual reading. After freeing all individual readings, it
|
|
* checks if the main readings array is not NULL and frees it as well.
|
|
*
|
|
* @return true if the main readings array was successfully freed, false otherwise.
|
|
*/
|
|
bool freeReadings()
|
|
{
|
|
for (int i = 0; i < sensorsNumber; i++)
|
|
{
|
|
free(readings[i]);
|
|
}
|
|
|
|
if (readings != NULL)
|
|
{
|
|
free(readings);
|
|
readings = NULL;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get the number of sensors.
|
|
*
|
|
* This function returns the total number of sensors currently available.
|
|
*
|
|
* @return int The number of sensors.
|
|
*/
|
|
// Get the number of sensors
|
|
int getSensorsNumber()
|
|
{
|
|
return sensorsNumber;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the sliding window size.
|
|
*
|
|
* This function returns the current size of the sliding window used in data acquisition.
|
|
*
|
|
* @return The size of the sliding window.
|
|
*/
|
|
// Get the sliding window size
|
|
int getSlidingWindowSize()
|
|
{
|
|
return slidingWindowSize;
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if the sliding window for a given sensor is full.
|
|
*
|
|
* This function iterates through the readings of a specified sensor and
|
|
* determines if all entries in the sliding window are non-zero, indicating
|
|
* that the window is full.
|
|
*
|
|
* @param sensorIndex The index of the sensor to check.
|
|
* @return true if the sliding window is full (all entries are non-zero),
|
|
* false otherwise.
|
|
*/
|
|
// Control on the fullness of the sliding window
|
|
bool isFull(int sensorIndex)
|
|
{
|
|
for (int i = 0; i < slidingWindowSize; i++)
|
|
{
|
|
if (readings[sensorIndex][i] == 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieves the last reading from the specified sensor.
|
|
*
|
|
* This function returns the most recent reading from the sensor identified by
|
|
* the given index. If the sensor's data buffer is full, it returns the last
|
|
* value in the buffer. Otherwise, it returns the first value.
|
|
*
|
|
* @param sensorIndex The index of the sensor to retrieve the reading from.
|
|
* @return The last reading from the specified sensor.
|
|
*/
|
|
float getLastReading(int sensorIndex)
|
|
{
|
|
if (isFull(sensorIndex))
|
|
{
|
|
return readings[sensorIndex][slidingWindowSize - 1];
|
|
}
|
|
else
|
|
{
|
|
return readings[sensorIndex][0];
|
|
}
|
|
}
|
|
|
|
static int lastSensorIndex = -1;
|
|
|
|
/**
|
|
* @brief Adds a new sensor reading to the data acquisition system.
|
|
*
|
|
* This function updates the readings for the sensors in a circular buffer manner.
|
|
* If the buffer for a sensor is full, it shifts the readings to make space for the new value.
|
|
* If the buffer is not full, it adds the new value to the first available position.
|
|
*
|
|
* @param value The new sensor reading to be added.
|
|
*/
|
|
void addReading(float value)
|
|
{
|
|
lastSensorIndex = (lastSensorIndex + 1) % sensorsNumber;
|
|
int sensorIndex = lastSensorIndex;
|
|
|
|
if (isFull(sensorIndex))
|
|
{
|
|
for (int i = 0; i < slidingWindowSize - 1; i++)
|
|
{
|
|
readings[sensorIndex][i] = readings[sensorIndex][i + 1];
|
|
}
|
|
readings[sensorIndex][slidingWindowSize - 1] = value;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < slidingWindowSize; i++)
|
|
{
|
|
if (readings[sensorIndex][i] == 0)
|
|
{
|
|
readings[sensorIndex][i] = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the average reading for a specified sensor.
|
|
*
|
|
* This function computes the average of the readings stored in a sliding window
|
|
* for the sensor specified by the sensorIndex parameter. If the sliding window
|
|
* is not full, the function prints a message and returns 0.
|
|
*
|
|
* @param sensorIndex The index of the sensor for which the average reading is to be calculated.
|
|
* @return The average reading of the specified sensor if the sliding window is full; otherwise, returns 0.
|
|
*/
|
|
float getAverageOnSensor(int sensorIndex)
|
|
{
|
|
if (isFull(sensorIndex) == false)
|
|
{
|
|
printf("The sliding window is not full\n");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
float sum = 0;
|
|
for (int i = 0; i < slidingWindowSize; i++)
|
|
{
|
|
sum += readings[sensorIndex][i];
|
|
}
|
|
return sum / slidingWindowSize;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the average reading from all sensors.
|
|
*
|
|
* This function iterates through all available sensors, retrieves the last reading
|
|
* from each sensor, and calculates the average of these readings.
|
|
*
|
|
* @return The average reading from all sensors as a float.
|
|
*/
|
|
float getAverageOnAllSensors()
|
|
{
|
|
float sum = 0;
|
|
for (int i = 0; i < sensorsNumber; i++)
|
|
{
|
|
sum += getLastReading(i);
|
|
}
|
|
return sum / sensorsNumber;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the overall average of sensor readings.
|
|
*
|
|
* This function iterates through all sensors and their respective sliding windows
|
|
* to calculate the overall average of the readings. It first checks if the sliding
|
|
* window for each sensor is full. If any sliding window is not full, it prints a
|
|
* message indicating which sensor's sliding window is not full and returns 0.
|
|
* Otherwise, it sums up all the readings from all sensors and calculates the average.
|
|
*
|
|
* @return The overall average of the sensor readings if all sliding windows are full,
|
|
* otherwise returns 0.
|
|
*/
|
|
float getOverallAverage()
|
|
{
|
|
float sum = 0;
|
|
int totalReadings = 0;
|
|
for (int i = 0; i < sensorsNumber; i++)
|
|
{
|
|
if (!isFull(i))
|
|
{
|
|
printf("The sliding window for sensor %d is not full\n", i);
|
|
return 0;
|
|
}
|
|
for (int j = 0; j < slidingWindowSize; j++)
|
|
{
|
|
sum += readings[i][j];
|
|
totalReadings++;
|
|
}
|
|
}
|
|
return sum / totalReadings;
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the standard deviation of sensor readings.
|
|
*
|
|
* This function computes the standard deviation of the readings from a specified sensor.
|
|
* It first checks if the sliding window for the sensor is full. If not, it prints a message
|
|
* and returns 0. If the sliding window is full, it calculates the average of the readings,
|
|
* then computes the sum of the squared differences between each reading and the average.
|
|
* Finally, it returns the square root of the average of these squared differences.
|
|
*
|
|
* @param sensorIndex The index of the sensor for which the standard deviation is to be calculated.
|
|
* @return The standard deviation of the sensor readings, or 0 if the sliding window is not full.
|
|
*/
|
|
float getStandardDeviationOnSensor(int sensorIndex)
|
|
{
|
|
if (isFull(sensorIndex) == false)
|
|
{
|
|
printf("The sliding window is not full\n");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
float sum = 0;
|
|
float average = getAverageOnSensor(sensorIndex);
|
|
for (int i = 0; i < slidingWindowSize; i++)
|
|
{
|
|
sum += pow(readings[sensorIndex][i] - average, 2);
|
|
}
|
|
return sqrt(sum / slidingWindowSize);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the standard deviation of the readings from all sensors.
|
|
*
|
|
* This function computes the standard deviation of the sensor readings by first
|
|
* calculating the average of all sensor readings, then summing the squared
|
|
* differences between each reading and the average, and finally taking the
|
|
* square root of the average of these squared differences.
|
|
*
|
|
* @return The standard deviation of the sensor readings.
|
|
*/
|
|
float getStandardDeviationOnAllSensors()
|
|
{
|
|
float sum = 0;
|
|
float average = getAverageOnAllSensors();
|
|
for (int i = 0; i < sensorsNumber; i++)
|
|
{
|
|
float lastReading = getLastReading(i);
|
|
sum += pow(lastReading - average, 2);
|
|
}
|
|
|
|
return sqrt(sum / sensorsNumber);
|
|
}
|
|
|
|
/**
|
|
* @brief Detects anomalies in sensor readings based on a given average and standard deviation.
|
|
*
|
|
* This function calculates the upper and lower thresholds for anomaly detection using a 97% confidence interval.
|
|
* It then iterates through the sensor readings within a sliding window and counts the number of outliers that fall
|
|
* outside the calculated thresholds.
|
|
*
|
|
* @param average The average value of the sensor readings.
|
|
* @param standardDeviation The standard deviation of the sensor readings.
|
|
*
|
|
* @note The function uses a confidence interval multiplier of 2.17 to determine the thresholds.
|
|
* @note The variable `outlierCount` is used to store the number of detected outliers.
|
|
* @note The variables `slidingWindowSize` and `sensorsNumber` are assumed to be defined globally.
|
|
* @note The 2D array `readings` is assumed to contain the sensor data.
|
|
*/
|
|
void anomalyDetect(float average, float standardDeviation)
|
|
{
|
|
float upperThreshold = average + 2.17 * standardDeviation; // 97% confidence interval
|
|
float lowerThreshold = average - 2.17 * standardDeviation; // Lower bound for anomaly detection
|
|
outlierCount = 0;
|
|
for (int j = 0; j < slidingWindowSize; j++)
|
|
{
|
|
for (int i = 0; i < sensorsNumber; i++)
|
|
{
|
|
if (readings[i][j] > upperThreshold || readings[i][j] < lowerThreshold)
|
|
{
|
|
outlierCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the overall standard deviation of sensor readings.
|
|
*
|
|
* This function computes the overall standard deviation of the readings from multiple sensors.
|
|
* It first checks if the sliding window for each sensor is full. If any sensor's sliding window
|
|
* is not full, it prints a message and returns 0. Otherwise, it calculates the sum of the squared
|
|
* differences between each reading and the overall average, and then computes the standard deviation.
|
|
* The function also calls `anomalyDetect` with the calculated average and standard deviation.
|
|
*
|
|
* @return The overall standard deviation of the sensor readings. Returns 0 if any sensor's sliding window is not full.
|
|
*/
|
|
float getOverallStandardDeviation()
|
|
{
|
|
float sum = 0;
|
|
int totalReadings = 0;
|
|
float totalAverage = getOverallAverage();
|
|
for (int i = 0; i < sensorsNumber; i++)
|
|
{
|
|
if (!isFull(i))
|
|
{
|
|
printf("The sliding window for sensor %d is not full\n", i);
|
|
return 0;
|
|
}
|
|
for (int j = 0; j < slidingWindowSize; j++)
|
|
{
|
|
sum += pow(readings[i][j] - getOverallAverage(), 2);
|
|
totalReadings++;
|
|
}
|
|
}
|
|
|
|
float totalStandardDeviation = sqrt(sum / totalReadings);
|
|
|
|
anomalyDetect(totalAverage, totalStandardDeviation);
|
|
|
|
return totalStandardDeviation;
|
|
}
|
|
|
|
/**
|
|
* @brief Retrieves the current count of outliers.
|
|
*
|
|
* This function returns the number of outliers detected and stored in the
|
|
* variable `outlierCount`.
|
|
*
|
|
* @return The number of outliers.
|
|
*/
|
|
int getOutlierCount()
|
|
{
|
|
return outlierCount;
|
|
}
|
|
|
|
Metrics getMetrics(float **readings, int sensorNumber, int slidingWindow)
|
|
{
|
|
Metrics metrics;
|
|
|
|
float average = 0;
|
|
float standardDeviation = 0;
|
|
int outlierCount = 0;
|
|
int *faultySensors = (int *)malloc(sensorNumber * sizeof(int));
|
|
|
|
for (int i = 0; i < sensorNumber; i++)
|
|
{
|
|
faultySensors[i] = 0;
|
|
}
|
|
|
|
float sum = 0;
|
|
|
|
for (int j = 0; j < slidingWindow; j++)
|
|
{
|
|
for (int i = 0; i < sensorNumber; i++)
|
|
{
|
|
sum += readings[i][j];
|
|
}
|
|
}
|
|
|
|
average = sum / (sensorNumber * slidingWindow);
|
|
|
|
for (int j = 0; j < slidingWindow; j++)
|
|
{
|
|
for (int i = 0; i < sensorNumber; i++)
|
|
{
|
|
standardDeviation += pow(readings[i][j] - average, 2);
|
|
}
|
|
}
|
|
|
|
standardDeviation = sqrt(standardDeviation / (sensorNumber * slidingWindow));
|
|
for (int j = 0; j < slidingWindow; j++)
|
|
{
|
|
for (int i = 0; i < sensorNumber; i++)
|
|
{
|
|
if (readings[i][j] > average + 2.17 * standardDeviation || readings[i][j] < average - 2.17 * standardDeviation)
|
|
{
|
|
outlierCount++;
|
|
faultySensors[i]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int faultySensorsCount = 0;
|
|
for(int i = 0; i < sensorNumber; i++)
|
|
{
|
|
if(faultySensors[i] >= 0.001 * slidingWindow)
|
|
{
|
|
faultySensorsCount++;
|
|
}
|
|
}
|
|
|
|
int *possiblyFaultySensors = (int *)malloc(faultySensorsCount * sizeof(int));
|
|
|
|
int j = 0;
|
|
for (int i = 0; i < faultySensorsCount; i++)
|
|
{
|
|
if (faultySensors[i] >= 0.001 * slidingWindow)
|
|
{
|
|
possiblyFaultySensors[j] = i;
|
|
}
|
|
}
|
|
|
|
metrics.mean = average;
|
|
metrics.standardDeviation = standardDeviation;
|
|
metrics.possibleFaultySensor = possiblyFaultySensors;
|
|
free(faultySensors);
|
|
return metrics;
|
|
} |