Can Arduino IoT cloud store images from remote ESP32 cam?

Hi. I'm looking for a low cost way to automatically store and view images online that are taken from my esp32 cam. The camera would be on a different WiFi network from where I want to view the image. Ideally, I'd like to view from my phone. Is this possible with the Arduino IoT cloud?
Thanks!

It's likely possible. However, split the total project into smaller parts. Then search for partial solutions for those parts. Finally integrate the parts into one system.

1 Like

I have several cams FTP their images to a Raspberry Pi (RPi). The RPi takes care of image processing and placing the images on our website.

Here is my code.

#include "sdkconfig.h" // used for log printing
#include "esp_system.h"
#include "freertos/FreeRTOS.h" //freeRTOS items to be used
#include "freertos/task.h"
#include "certs.h"
#include "esp_camera.h"
#include "soc/soc.h"           // Disable brownout problems
#include "soc/rtc_cntl_reg.h"  // Disable brownout problems
#include "driver/rtc_io.h"
#include <WiFi.h>
#include <WiFiClient.h>
#include "ESP32_FTPClient.h"
#include <PubSubClient.h>
#include <ESP32Time.h>
//
WiFiClient      wifiClient; // do the WiFi instantiation thing
PubSubClient    MQTTclient( mqtt_server, mqtt_port, wifiClient ); //do the MQTT instantiation thing
ESP32_FTPClient ftp (ftp_server, ftp_user, ftp_pass, 5000, 2);
ESP32Time       rtc;
////
//#define evtDoMQTTParse  ( 1 << 0 ) // declare an event
//EventGroupHandle_t eg; // variable for the event group handle
////
SemaphoreHandle_t sema_MQTT_KeepAlive;
SemaphoreHandle_t sema_mqttOK;
////
QueueHandle_t xQ_Message; // payload and topic queue of MQTT payload and topic
const int payloadSize = 300;
struct stu_message
{
  char payload [payloadSize] = {'\0'};
  String topic ;
} x_message;
////
int  mqttOK = 0;
bool TimeSet = false;
bool FlashMode = false;
//
void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
{
  switch (event) {
    case SYSTEM_EVENT_STA_CONNECTED:
      log_i("Connected to WiFi access point");
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      log_i("Disconnected from WiFi access point");
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:
      log_i("WiFi client disconnected");
      break;
    default: break;
  }
} // void IRAM_ATTR WiFiEvent(WiFiEvent_t event)
//
void IRAM_ATTR mqttCallback(char* topic, byte * payload, unsigned int length)
{
  // clear locations
  memset( x_message.payload, '\0', payloadSize );
  x_message.topic = ""; //clear string buffer
  x_message.topic.concat( topic );
  int i = 0;
  for ( i; i < length; i++)
  {
    x_message.payload[i] = ((char)payload[i]);
  }
  x_message.payload[i] = '\0';
  xQueueOverwrite( xQ_Message, (void *) &x_message );// send data to queue
} // void mqttCallback(char* topic, byte* payload, unsigned int length)
////
void setup()
{
  pinMode( GPIO_NUM_4, OUTPUT);
  digitalWrite( GPIO_NUM_4, LOW);
  //
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  //
  //eg = xEventGroupCreate(); // get an event group handle
  //
  x_message.topic.reserve( payloadSize );
  //
  xQ_Message  = xQueueCreate( 1, sizeof(stu_message) );
  //
  sema_mqttOK    =  xSemaphoreCreateBinary();
  xSemaphoreGive( sema_mqttOK );
  //
  xTaskCreatePinnedToCore( MQTTkeepalive, "MQTTkeepalive", 7000, NULL, 3, NULL, 1 );
  xTaskCreatePinnedToCore( fparseMQTT, "fparseMQTT", 9000, NULL, 4, NULL, 1 ); // assign all to core 1, WiFi in use.
  xTaskCreatePinnedToCore( fmqttWatchDog, "fmqttWatchDog", 3000, NULL, 2, NULL, 1 );
  if ( configInitCamera() )
  {
    log_i("        start camera task" );
    xTaskCreatePinnedToCore( capturePhoto_sendFTP, "capturePhoto_sendFTP", 60000, NULL, 6, NULL, 1 );
  } else {
    log_i( "       camera failed to initilize, rebooting in 5." );
    vTaskDelay( 2000 );
    ESP.restart();
  }
} // void setup()
////
void capturePhoto_sendFTP( void *pvParameters )
{
  TickType_t xLastWakeTime    = xTaskGetTickCount();
  const TickType_t xFrequency = 1000 * 5; //delay for mS
  //!!!!!Must wait for MQTT connection and proper setup of the sema_MQTT_KeepAlive!!!!
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 500 );
  }
  for (;;)
  {
    log_i( "tick");
    xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY );
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      camera_fb_t * fb = NULL; // pointer
      fb = esp_camera_fb_get();
      if (!fb)
      {
        log_i( "Camera capture failed" );
        esp_camera_fb_return(fb);
      } else
      {
        ftp.OpenConnection(); // try open FTP
        if ( ftp.isConnected() )
        {
          //try send file ftp
          ftp.ChangeWorkDir( ftp_path );
          ftp.DeleteFile( ftp_file_name );
          ftp.InitFile( ftp_file_type ); //"Type I"
          ftp.NewFile( ftp_file_name );
          ftp.WriteData( (unsigned char *)fb->buf, fb->len );
          ftp.CloseFile();
          ftp.CloseConnection();
        }
        esp_camera_fb_return(fb); //return the frame buffer back to the driver for reuse
      }
    }
    xSemaphoreGive( sema_MQTT_KeepAlive );
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete( NULL );
} //void capturePhoto_sendFTP( void *pvParameters )
////
void configureCameraSettings_grpE( int _lenC, int _Hmirror, int _Vflip, int _dcw, int _colorbar )
{
  sensor_t * s = esp_camera_sensor_get();
  s->set_lenc(s, _lenC);         // 0 = disable , 1 = enable
  s->set_hmirror(s, _Hmirror);   // 0 = disable , 1 = enable
  s->set_vflip(s, _Vflip);       // 0 = disable , 1 = enable
  s->set_dcw(s, _dcw);           // 0 = disable , 1 = enable
  ////s->set_colorbar(s, _colorbar); // 0 = disable , 1 = enable
}
////
void configureCameraSettings_grpD( int _gc, int _agc, int _celing, int _bpc, int _wpc, int _gma )
{
  sensor_t * s = esp_camera_sensor_get();
  s->set_gain_ctrl(s, _gc); // 0 = disable , 1 = enable
  s->set_agc_gain(s, _agc); // 0 to 30
  s->set_gainceiling(s, (gainceiling_t)_celing);  // 0 to 6
  s->set_bpc(s, _bpc);      // 0 = disable , 1 = enable
  s->set_wpc(s, _wpc);      // 0 = disable , 1 = enable
  s->set_raw_gma(s, _gma);  // 0 = disable , 1 = enable
}
////
void configureCameraSettings_grpC( int _exctl, int _aec2, int _ae, int _aec )
{
  sensor_t * s = esp_camera_sensor_get();
  s->set_exposure_ctrl(s, _exctl); // 0 = disable , 1 = enable
  s->set_aec2(s, _aec2);           // 0 = disable , 1 = enable
  s->set_ae_level(s, _ae);         // -2 to 2
  ////s->set_aec_value(s, _aec);       // 0 to 1200 CAUSES BLACK IMAGE????
}
////
void configureCameraSettings_grpB( int se, int wb, int awb, int wbmode )
{
  sensor_t * s = esp_camera_sensor_get();
  s->set_special_effect(s, se); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  s->set_whitebal(s, wb);       // 0 = disable , 1 = enable
  s->set_awb_gain(s, awb);      // 0 = disable , 1 = enable
  s->set_wb_mode(s, wbmode);    // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
}
////
void configureCameraSettings_grpA(int brightness, int contrast, int saturation )
{
  if ( ((brightness > -3) && (brightness < 3)) && ((contrast > -3) && (contrast < 3)) && ((saturation > -3) && (saturation < 3)) )
  {
    sensor_t * s = esp_camera_sensor_get();
    s->set_brightness( s, brightness ); // -2 to 2
    s->set_contrast( s, contrast );    // -2 to 2
    s->set_saturation( s, saturation );  // -2 to 2
  }
} //void configureCameraSettings()
////
void configureCameraSettings()
{
  sensor_t * s = esp_camera_sensor_get(); //see certs.h for more info
  s->set_brightness(s, -1);     // -2 to 2 **************************
  s->set_contrast(s, 0);       // -2 to 2
  s->set_saturation(s, 0);     // -2 to 2
  s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  s->set_whitebal(s, 1);       // 0 = disable , 1 = enable
  s->set_awb_gain(s, 1);       // 0 = disable , 1 = enable
  s->set_wb_mode(s, 0);        // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  s->set_exposure_ctrl(s, 1);  // 0 = disable , 1 = enable
  s->set_aec2(s, 0);           // 0 = disable , 1 = enable
  s->set_ae_level(s, 0);       // -2 to 2
  s->set_aec_value(s, 300);    // 0 to 1200
  s->set_gain_ctrl(s, 1);      // 0 = disable , 1 = enable
  s->set_agc_gain(s, 0);       // 0 to 30
  s->set_gainceiling(s, (gainceiling_t)0);  // 0 to 6
  s->set_bpc(s, 0);            // 0 = disable , 1 = enable
  s->set_wpc(s, 1);            // 0 = disable , 1 = enable
  s->set_raw_gma(s, 1);        // 0 = disable , 1 = enable
  s->set_lenc(s, 1);           // 0 = disable , 1 = enable
  s->set_hmirror(s, 0);        // 0 = disable , 1 = enable
  s->set_vflip(s, 0);          // 0 = disable , 1 = enable
  s->set_dcw(s, 1);            // 0 = disable , 1 = enable
  s->set_colorbar(s, 0);       // 0 = disable , 1 = enable
} //void configureCameraSettings()
////
bool configInitCamera()
{
  camera_config_t config = {}; // Stores the camera configuration parameters
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer   = LEDC_TIMER_0;
  config.pin_d0       = GPIO_NUM_5; //Y2
  config.pin_d1       = GPIO_NUM_18; //Y3
  config.pin_d2       = GPIO_NUM_19; //Y4
  config.pin_d3       = GPIO_NUM_21; //Y5
  config.pin_d4       = GPIO_NUM_36; //Y6
  config.pin_d5       = GPIO_NUM_39; //Y7
  config.pin_d6       = GPIO_NUM_34; //Y8
  config.pin_d7       = GPIO_NUM_35; // Y9
  config.pin_xclk     = GPIO_NUM_0; //XCLK
  config.pin_pclk     = GPIO_NUM_22; //PCLK
  config.pin_vsync    = GPIO_NUM_25; //VSSYNC
  config.pin_href     = GPIO_NUM_23; // HREF
  config.pin_sscb_sda = GPIO_NUM_26; //SIOD
  config.pin_sscb_scl = GPIO_NUM_27; //SIOC
  config.pin_pwdn     = GPIO_NUM_32; //PWDN
  config.pin_reset    = -1; //RESET
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; ////YUV422,GRAYSCALE,RGB565,JPEG
  config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
  /*
    FRAMESIZE_UXGA (1600 x 1200)
    FRAMESIZE_QVGA (320 x 240)
    FRAMESIZE_CIF (352 x 288)
    FRAMESIZE_VGA (640 x 480)
    FRAMESIZE_SVGA (800 x 600)
    FRAMESIZE_XGA (1024 x 768)
    FRAMESIZE_SXGA (1280 x 1024)
  */
  config.jpeg_quality = 10; //0-63 lower number means higher quality
  /*
   * The image quality (jpeg_quality) can be a number between 0 and 63. 
   * A lower number means a higher quality. 
   * However, very low numbers for image quality, 
   * specially at higher resolution can make the ESP32-CAM to crash or it may not be able to take the photos properly.
   * 
   * So, if you notice that the images taken with the ESP32-CAM are cut in half, or with strange colors, 
   * that’s probably a sign that you need to lower the quality (select a higher number).
   */
  config.fb_count = 2;
  // Initialize the Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    log_i("Camera init failed with error %d", err );
    return false;
  } else {
    configureCameraSettings();
    return true;
  }
} //void configInitCamera()
////
void connectToWiFi()
{
  int TryCount = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    TryCount++;
    WiFi.disconnect();
    WiFi.begin( SSID, PASSWORD );
    vTaskDelay( 2500 );
    if ( TryCount == 10 )
    {
      ESP.restart();
    }
  }
  WiFi.onEvent( WiFiEvent );
}
////
void MQTTkeepalive( void *pvParameters )
{
  sema_MQTT_KeepAlive   = xSemaphoreCreateBinary();
  xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
  MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 250; //delay for ms
  for (;;)
  {
    //check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
    if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
    {
      xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // !!!!!whiles MQTTlient.loop() is running no other mqtt operations should be in process!!!!!
      MQTTclient.loop();
      xSemaphoreGive( sema_MQTT_KeepAlive );
    }
    else {
      if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
      {
        connectToWiFi();
      }
      connectToMQTT();
    }
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete ( NULL );
}
////
void connectToMQTT()
{
  byte mac[5]; // create client ID from mac address
  WiFi.macAddress(mac); // get mac address
  String clientID = String(mac[0]) + String(mac[4]) ; // use mac address to create clientID
  while ( !MQTTclient.connected() )
  {
    MQTTclient.connect( clientID.c_str(), mqtt_username, mqtt_password );
    vTaskDelay( 250 );
  }
  MQTTclient.setCallback( mqttCallback );
  MQTTclient.subscribe( topicOK );
  MQTTclient.subscribe( topicGrpA );
  MQTTclient.subscribe( topicGrpB );
  MQTTclient.subscribe( topicGrpC );
  MQTTclient.subscribe( topicGrpD );
  MQTTclient.subscribe( topicGrpE );
  MQTTclient.subscribe( topicFlash );
} //void connectToMQTT()
//////
void fmqttWatchDog( void * paramater )
{
  int UpdateImeTrigger = 86400; //seconds in a day
  int UpdateTimeInterval = 85000; // get another reading when = UpdateTimeTrigger
  int maxNonMQTTresponse = 3;
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 60000; //delay for mS
  for (;;)
  {
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    xSemaphoreTake( sema_mqttOK, portMAX_DELAY ); // update mqttOK
    mqttOK++;
    xSemaphoreGive( sema_mqttOK );
    if ( mqttOK >= maxNonMQTTresponse )
    {
      log_i( "mqtt watchdog rest" );
      vTaskDelay( 200 );
      ESP.restart();
    }
    UpdateTimeInterval++; // trigger new time get
    if ( UpdateTimeInterval >= UpdateImeTrigger )
    {
      TimeSet = false; // sets doneTime to false to get an updated time after a days count of seconds
      UpdateTimeInterval = 0;
    }
  }
  vTaskDelete( NULL );
} //void fmqttWatchDog( void * paramater )
////
void fparseMQTT( void *pvParameters )
{
  struct stu_message px_message;
  for (;;)
  {
    if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
    {
      xSemaphoreTake( sema_mqttOK, portMAX_DELAY );
      mqttOK = 0;
      xSemaphoreGive( sema_mqttOK );
      if ( !TimeSet)
      {
        if ( String(px_message.topic) == topicOK )
        {
          String temp = "";
          temp        = px_message.payload[0];
          temp        += px_message.payload[1];
          temp        += px_message.payload[2];
          temp        += px_message.payload[3];
          int year    =  temp.toInt();
          temp        = "";
          temp        = px_message.payload[5];
          temp        += px_message.payload[6];
          int month   =  temp.toInt();
          temp        = "";
          temp        = px_message.payload[8];
          temp        += px_message.payload[9];
          int day     =  temp.toInt();
          temp        = "";
          temp        = px_message.payload[11];
          temp        += px_message.payload[12];
          int hour    =  temp.toInt();
          temp        = "";
          temp        = px_message.payload[14];
          temp        += px_message.payload[15];
          int min     =  temp.toInt();
          rtc.setTime( 0, min, hour, day, month, year );
          log_i( "%s   rtc  %s ", px_message.payload, rtc.getTime() );
          TimeSet     = true;
        }
      }
      if ( String(px_message.topic) == topicGrpA )
      {
        //finding first letter
        String sTmp = String( px_message.payload );
        int commaIndex = sTmp.indexOf(',');
        if ( sTmp.substring(0, commaIndex) == "a" );
        {
          int commaIndex = sTmp.indexOf(',');
          int brt =  (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int ctrast = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) ); // chop off begining of message
          int sat = (sTmp.substring(0, commaIndex)).toInt();
          configureCameraSettings_grpA( brt, ctrast, sat );
        }
      }
      if ( String(px_message.topic) == topicGrpB )
      {
        String sTmp = String( px_message.payload );
        int commaIndex = sTmp.indexOf(',');
        if ( sTmp.substring(0, commaIndex) == "b" );
        {
          int commaIndex = sTmp.indexOf(',');
          int se =  (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int wbb = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int wba = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int wbm = (sTmp.substring(0, commaIndex)).toInt();
          configureCameraSettings_grpB( se, wbb, wba, wbm );
        }
      }
      if ( String(px_message.topic) == topicGrpC )
      {
        String sTmp = String( px_message.payload );
        int commaIndex = sTmp.indexOf(',');
        if ( sTmp.substring(0, commaIndex) == "c" );
        {
          int commaIndex = sTmp.indexOf(',');
          int ex =  (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int aec2 = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int ae = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int aec = (sTmp.substring(0, commaIndex)).toInt();
          configureCameraSettings_grpC( ex, aec2, ae, aec );
        }
      }
      if ( String(px_message.topic) == topicGrpD )
      {
        String sTmp = String( px_message.payload );
        int commaIndex = sTmp.indexOf(',');
        if ( sTmp.substring(0, commaIndex) == "d" );
        {
          int commaIndex = sTmp.indexOf(',');
          int gc =  (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int agc = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int celing = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int bpc = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int wpc = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int gma = (sTmp.substring(0, commaIndex)).toInt();
          configureCameraSettings_grpD( gc, agc, celing, bpc, wpc, gma );
        }
      }
      if ( String(px_message.topic) == topicGrpE )
      {
        String sTmp = String( px_message.payload );
        int commaIndex = sTmp.indexOf(',');
        if ( sTmp.substring(0, commaIndex) == "e" );
        {
          int commaIndex = sTmp.indexOf(',');
          int lenC =  (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int Hmirror = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int Vflip = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int dcw = (sTmp.substring(0, commaIndex)).toInt();
          sTmp.remove( 0, (commaIndex + 1) );
          int colorbar = (sTmp.substring(0, commaIndex)).toInt();
          configureCameraSettings_grpE( lenC, Hmirror, Vflip, dcw, colorbar );
        }
      }
      if ( String(px_message.topic) == topicFlash )
      {
        FlashMode = bool(px_message.payload);
      }
    } //if ( xQueueReceive(xQ_Message, &px_message, portMAX_DELAY) == pdTRUE )
  } //for(;;)
  vTaskDelete( NULL );
} // void fparseMQTT( void *pvParameters )
////
void loop() {}

Oh, the code will also allow me to remotly change camera settings.

2 Likes

Thanks Idahowalker. FTP to RPi seems to be the way to go.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.