Using LR to predict the next hours air pressure from a ESP32:
void fDoTrends( void *pvParameters )
{
const int magicNumber = 96;
double values[2];
int lrCount = 0;
float lrData = 0.0f;
float DataPoints[magicNumber] = {0.0f};
float TimeStamps[magicNumber] = {0.0f};
float dpHigh = 702.0f;
float dpLow = 683.0f;
float dpAtom = 0.0f;
float dpMean = 0.0f; //data point mean
float tsAtom = 0.0f;
float tsUnit = 0.0f;
float tsMean = 0.0f;
bool dpRecalculate = true;
bool FirstTimeMQTT = true;
String apInfo = "";
apInfo.reserve( 150 );
for (;;)
{
if ( xQueueReceive(xQ_lrData, &lrData, portMAX_DELAY) == pdTRUE )
{
apInfo.concat( String((float)xTaskGetTickCount() / 1000.0f) );
apInfo.concat( "," );
apInfo.concat( String(lrData) );
apInfo.concat( ",0.0" );
apInfo.concat( ",0.0" );
if ( MQTTclient.connected() )
{
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
MQTTclient.publish( topicPressureInfo, apInfo.c_str() );
vTaskDelay( 1 );
xSemaphoreGive( sema_MQTT_KeepAlive );
}
xQueueSend( xQ_pMessage, (void *) &apInfo, portMAX_DELAY ); // wait for queue space to become available
apInfo = "";
//find dpHigh and dpLow, collects historical high and low data points, used for data normalization
if ( lrData > dpHigh )
{
dpHigh = lrData;
dpRecalculate = true;
}
if ( lrData < dpLow )
{
dpLow = lrData;
dpRecalculate = true;
}
if ( lrCount != magicNumber )
{
DataPoints[lrCount] = lrData;
TimeStamps[lrCount] = (float)xTaskGetTickCount() / 1000.0f;
log_i( "lrCount %d TimeStamp %f lrData %f", lrCount, TimeStamps[lrCount], DataPoints[lrCount] );
lrCount++;
} else {
//shift datapoints collected one place to the left
for ( int i = 0; i < magicNumber; i++ )
{
DataPoints[i] = DataPoints[i + 1];
TimeStamps[i] = TimeStamps[i + 1];
}
//insert new data points and time stamp (ts) at the end of the data arrays
DataPoints[magicNumber - 1] = lrData;
TimeStamps[magicNumber - 1] = (float)xTaskGetTickCount() / 1000.0f;
lr.Reset(); //reset the LinearRegression Parameters
// use dpHigh and dpLow to calculate data mean atom for normalization
if ( dpRecalculate )
{
dpAtom = 1.0f / (dpHigh - dpLow); // a new high or low data point has been found adjust mean dpAtom
dpRecalculate = false;
}
//timestamp mean is ts * (1 / ts_Firstcell - ts_Lastcell[magicNumber]). ts=time stamp
tsAtom = 1.0f / (TimeStamps[magicNumber - 1] - TimeStamps[0]); // no need to do this part of the calculation every for loop ++
for (int i = 0; i < magicNumber; i++)
{
dpMean = (DataPoints[i] - dpLow) * dpAtom;
tsMean = TimeStamps[i] * tsAtom;
lr.Data( tsMean, dpMean ); // train lr
//send to mqtt the first time
if ( FirstTimeMQTT )
{
apInfo.concat( String(TimeStamps[i]) );
apInfo.concat( "," );
apInfo.concat( String(DataPoints[i]) );
apInfo.concat( "," );
apInfo.concat( String(tsMean) );
apInfo.concat( "," );
apInfo.concat( String(dpMean) );
xQueueSend( xQ_pMessage, (void *) &apInfo, portMAX_DELAY ); // wait for queue space to become available
apInfo = "";
}
}
if ( !FirstTimeMQTT )
{
apInfo.concat( String(TimeStamps[magicNumber - 1]) );
apInfo.concat( "," );
apInfo.concat( String(DataPoints[magicNumber - 1]) );
apInfo.concat( "," );
apInfo.concat( String(tsMean) );
apInfo.concat( "," );
apInfo.concat( String(dpMean) );
xQueueSend( xQ_pMessage, (void *) &apInfo, portMAX_DELAY );
apInfo = "";
}
FirstTimeMQTT = false;
lr.Parameters( values );
//calculate
tsUnit = TimeStamps[magicNumber - 1] - TimeStamps[magicNumber - 2]; //get the time stamp quantity
tsUnit += TimeStamps[magicNumber - 1]; //get a future time
tsUnit *= tsAtom; //setting time units to the same scale
log_i( "Calculate next x using y = %f", lr. Calculate( tsUnit ) ); //calculate next datapoint using time stamp
log_i( "Correlation: %f Values: Y=%f and *X + %f ", lr.Correlation(), values[0], values[1] ); // correlation is the strength and direction of the relationship
//calculate datapoint for current time stamp, use current data point against calculated datapoint to get error magnatude and direction.
//log_i( "lr.Error( x_pMessage.TimeStamp, x_pMessage.nDataPoint ) %f", lr.Error(x_pMessage.nTimeStamp, x_pMessage.nDataPoint) ); //
}
log_i( "fDoTrends high watermark % d", uxTaskGetStackHighWaterMark( NULL ) );
} //if ( xQueueReceive(xQ_lrData, &lrData, portMAX_DELAY) == pdTRUE )
} //for(;;)
vTaskDelete ( NULL );
} //void fDoTrends( void *pvParameters )
Using this library GitHub - cubiwan/Regressino: Calculate potential, exponential, logarithmic, lineal and logistic regressio in Arduino.