/* Snippet content: Takes pictures with ESP32-CAM, saves images to MicroSD Card. Measures air temperature and relative humidity from SHT85. We use the ESP-CAM for the SHT85 readout because of the limited wire length that can be used for I2C sensors - the camera sits at the top of the mast next to the SHT85 sensor. ESP-CAM code partially copied from https://dronebotworkshop.com/esp32-cam-microsd/ NOTE: To flash the ESP-CAM, pin IO0 has to be connected to GND! TODO: because of this limitation, it would be nice to implement over-the-air updates for the camera at one point. */ // Include Required Libraries // I2C: SHT85, RTC #include "ESPNow.hpp" #include "NoDataAvailableException.hpp" #include "RTClib.h" // adafruit/RTClib @^2.1.1 #include "Sht85.hpp" #include "ram_caching.hpp" #include <Arduino.h> #include <Wire.h> // Camera libraries #include "driver/rtc_io.h" #include "esp_camera.h" #include "soc/rtc_cntl_reg.h" #include "soc/soc.h" // MicroSD Libraries #include "FS.h" #include "SD_MMC.h" // Pin definitions for CAMERA_MODEL_AI_THINKER #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 // Pin definitions for I2C (SHT85, RTC) // This is different from the pins on the ESP32-C3-DevKit boards! #define SDA 13 #define SCL 12 // LED control #define LEDpin 4 // Deep Sleep Settings #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */ #define TIME_TO_SLEEP 10 /* Time ESP32 will go to sleep (in seconds) */ // Counting files on sd card for saving in the name of each image int fileCountOnSD = 0; // for counting files // string for saving the time char time_string[20]; // number of images to take: the last one is saved // this is done because the first few images have a green tint otherwise int img_number = 3; RTC_DS3231 rtc; // I2C address: 0x68 camera_config_t camera_config; // camera configuration parameters void camera_configESPCamera() { // Configure Camera parameters camera_config.ledc_channel = LEDC_CHANNEL_0; camera_config.ledc_timer = LEDC_TIMER_0; camera_config.pin_d0 = Y2_GPIO_NUM; camera_config.pin_d1 = Y3_GPIO_NUM; camera_config.pin_d2 = Y4_GPIO_NUM; camera_config.pin_d3 = Y5_GPIO_NUM; camera_config.pin_d4 = Y6_GPIO_NUM; camera_config.pin_d5 = Y7_GPIO_NUM; camera_config.pin_d6 = Y8_GPIO_NUM; camera_config.pin_d7 = Y9_GPIO_NUM; camera_config.pin_xclk = XCLK_GPIO_NUM; camera_config.pin_pclk = PCLK_GPIO_NUM; camera_config.pin_vsync = VSYNC_GPIO_NUM; camera_config.pin_href = HREF_GPIO_NUM; camera_config.pin_sccb_sda = SIOD_GPIO_NUM; camera_config.pin_sccb_scl = SIOC_GPIO_NUM; camera_config.pin_pwdn = PWDN_GPIO_NUM; camera_config.pin_reset = RESET_GPIO_NUM; camera_config.xclk_freq_hz = 20000000; camera_config.pixel_format = PIXFORMAT_JPEG; // Choices are YUV422, GRAYSCALE, RGB565, JPEG // Select lower framesize if the camera doesn't support PSRAM if (psramFound()) { camera_config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA camera_config.jpeg_quality = 10; // 10-63 lower number means higher quality camera_config.fb_count = 2; } else { camera_config.frame_size = FRAMESIZE_SVGA; camera_config.jpeg_quality = 12; camera_config.fb_count = 1; } // Initialize the Camera esp_err_t err = esp_camera_init(&camera_config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } // Camera quality adjustments sensor_t *s = esp_camera_sensor_get(); // TODO: this is copied from an online tutorial, it's possible that we don't need this at all! // BRIGHTNESS (-2 to 2) s->set_brightness(s, 0); // CONTRAST (-2 to 2) s->set_contrast(s, 0); // SATURATION (-2 to 2) s->set_saturation(s, 0); // SPECIAL EFFECTS (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - // Sepia) s->set_special_effect(s, 0); // WHITE BALANCE (0 = Disable , 1 = Enable) s->set_whitebal(s, 1); // AWB GAIN (0 = Disable , 1 = Enable) s->set_awb_gain(s, 1); // WB MODES (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) s->set_wb_mode(s, 0); // EXPOSURE CONTROLS (0 = Disable , 1 = Enable) s->set_exposure_ctrl(s, 1); // AEC2 (0 = Disable , 1 = Enable) s->set_aec2(s, 0); // AE LEVELS (-2 to 2) s->set_ae_level(s, 0); // AEC VALUES (0 to 1200) s->set_aec_value(s, 300); // GAIN CONTROLS (0 = Disable , 1 = Enable) s->set_gain_ctrl(s, 1); // AGC GAIN (0 to 30) s->set_agc_gain(s, 0); // GAIN CEILING (0 to 6) s->set_gainceiling(s, (gainceiling_t)0); // BPC (0 = Disable , 1 = Enable) s->set_bpc(s, 0); // WPC (0 = Disable , 1 = Enable) s->set_wpc(s, 1); // RAW GMA (0 = Disable , 1 = Enable) s->set_raw_gma(s, 1); // LENC (0 = Disable , 1 = Enable) s->set_lenc(s, 1); // HORIZ MIRROR (0 = Disable , 1 = Enable) s->set_hmirror(s, 0); // VERT FLIP (0 = Disable , 1 = Enable) s->set_vflip(s, 0); // DCW (0 = Disable , 1 = Enable) s->set_dcw(s, 1); // COLOR BAR PATTERN (0 = Disable , 1 = Enable) s->set_colorbar(s, 0); } //SHTSensor sht(SHTSensor::SHT85); // I2C address: 0x44 void initMicroSDCard() { // Start the MicroSD card Serial.println("Mounting MicroSD Card"); if (!SD_MMC.begin("/sdcard", true)) { // the arguments disable the LED when using the SD card - leave them there! Serial.println("MicroSD Card Mount Failed"); return; } uint8_t cardType = SD_MMC.cardType(); if (cardType == CARD_NONE) { Serial.println("No MicroSD Card found"); return; } } void takeNewPhoto(String path) { // Take picture with the camera and save it // Take multiple pictures with a short break in between // necessary for the camera to auto-adjust settings camera_fb_t *fb; for (int i = 1; i <= img_number; i++) { // Setup frame buffer fb = esp_camera_fb_get(); // digitalWrite(LEDpin, LOW); // disable flash LED if needed if (!fb) { Serial.println("Camera capture failed"); return; } else { Serial.println("Camera capture successful"); } // TODO check if this delay is necessary once the SD-card todo below // is resolved. It's possible that it can be at least shortened. if (i == img_number) { delay(2000); } esp_camera_fb_return(fb); // Without this delay the image is corrupt // TODO this delay can possibly also be optimized. delay(1500); /* TODO: The SD card throws the following error when the capture fails: E (877528) sdmmc_cmd: sdmmc_write_sectors_dma: sdmmc_send_cmd returned 0x109 E (877528) diskio_sdmmc: sdmmc_write_blocks failed (265) This happens infrequently, say every 10th image or so. If this happens, one more new image should be taken and saved */ } // Save picture to microSD card fs::FS &fs = SD_MMC; File file = fs.open(path.c_str(), FILE_WRITE); if (!file) { Serial.println("Failed to open file in write mode"); } else { file.write(fb->buf, fb->len); // payload (image), payload length Serial.printf("Saved file to path: %s\n", path.c_str()); } // Close the file file.close(); // Return the frame buffer back to the driver for reuse esp_camera_fb_return(fb); } // Count files on SD card to give a meaningful count void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ Serial.printf("Listing directory: %s\n", dirname); File root = fs.open(dirname); if(!root){ Serial.println("Failed to open directory"); return; } if(!root.isDirectory()){ Serial.println("Not a directory"); return; } File file = root.openNextFile(); while(file){ fileCountOnSD += 1; //Serial.print(" FILE: "); //Serial.print(file.name()); //Serial.print(" SIZE: "); //Serial.println(file.size()); file = root.openNextFile(); } Serial.println("File Count on SD: "); Serial.println(fileCountOnSD); } void readRTC() { // This is used for a picture timestamp (name of the image) if (! rtc.begin()) { Serial.println("Couldnt find RTC!"); } else { Serial.println("Setting the time"); } DateTime now = rtc.now(); sprintf(time_string, "%04d-%02d-%02dT%02d-%02d-%02d", now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second()); Serial.println(time_string); } Sht85 sht85Sensor; void setup() { // control of the LED pin pinMode(LEDpin, OUTPUT); // Disable brownout detector WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Start Serial Serial.begin(115200); Serial.println("Wake Up!"); delay(1000); // Initialize the camera Serial.print("Initializing the camera module..."); camera_configESPCamera(); Serial.println("Camera OK!"); espnow_setup(); sht85Sensor.setup(); // before in loop // initiate I2C, read SHT+RTC, end I2C readRTC(); try { // out_data_drs26 data= dr26.readData(); // Serial.printf("data circumfrence"); // Serial.printf(String( data.circumferenceIncrement).c_str()); // auto messages = drs26.buildMessages(); auto messages = sht85Sensor.buildMessages(); // auto scdMessages = scd30.buildmessages(); for (const Message &message : messages) { if (message.send() != ESP_OK) { RtcMemory::store(message.getMessageAsMinifiedJsonString()); } delay(5000); if (!was_msg_received()) { RtcMemory::store(message.getMessageAsMinifiedJsonString()); } } } catch (const NoDataAvailableException &e) { std::cerr << e.what() << '\n'; } // Initialize the MicroSD Serial.print("Initializing the MicroSD card module... "); initMicroSDCard(); // Count files on SD card listDir(SD_MMC, "/", 0); // Path where new image will be saved in MicroSD card String path = "/Picture_" + String(fileCountOnSD) + ".jpg"; Serial.printf("Picture file name: %s\n", path.c_str()); // Take and save a picture takeNewPhoto(path); // Unmount SD card to free the pins for I2C use SD_MMC.end(); // Delay for specified period delay(1000); // Deep sleep esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); Serial.println("Setup ESP32 to sleep for " + String(TIME_TO_SLEEP) + " Seconds"); Serial.println("Going to sleep now"); delay(1000); Serial.flush(); gpio_hold_en((gpio_num_t)LEDpin); esp_deep_sleep_start(); } void loop() { }