Using ESP32-CAM as standalone controller

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.