├── esp32cam-google-drive.png ├── config.h ├── esp32.gs ├── LICENSE ├── README.md ├── Base64.h ├── Base64.cpp └── esp32cam-google-drive.ino /esp32cam-google-drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertSasak/esp32cam-google-drive/HEAD/esp32cam-google-drive.png -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #define FOLDER "ESP32" 2 | #define SCRIPT_ID "here comes very long script ID that you will copy from url of script.google.com" 3 | #define WIFI_NETWORK "your wifi network" 4 | #define PASSWORD "your wifi password" 5 | #define HOST "script.google.com" 6 | #define PORT 443 7 | #define SLEEP_TIME 300 // in seconds 8 | #define SLEEP_TIME_NIGHT 600 // in seconds 9 | #define NIGHT_START 19 // in hours 10 | #define NIGHT_END 6 // in hours 11 | #define WAITING_TIME 10 // Wait x seconds for response. 12 | #define OTA_WAITING_TIME 30 // in seconds 13 | #define FLIP false // Flip capture image -------------------------------------------------------------------------------- /esp32.gs: -------------------------------------------------------------------------------- 1 | function doPost(e) { 2 | const name = 3 | Utilities.formatDate(new Date(), 'GMT+0', 'yyyyMMdd-HHmmss') + '.jpg' 4 | const subFolderName = Utilities.formatDate(new Date(), 'GMT+0', 'yyyyMMdd') 5 | const folderName = e.parameters.folder || 'ESP32-CAM' 6 | const data = Utilities.base64Decode(e.postData.contents) 7 | const blob = Utilities.newBlob(data, 'image/jpg', name) 8 | 9 | let folder 10 | const folders = DriveApp.getFoldersByName(folderName) 11 | if (folders.hasNext()) { 12 | folder = folders.next() 13 | } else { 14 | folder = DriveApp.createFolder(folderName) 15 | } 16 | let subFolder 17 | const subFolders = folder.getFoldersByName(subFolderName) 18 | if (subFolders.hasNext()) { 19 | subFolder = subFolders.next() 20 | } else { 21 | subFolder = folder.createFolder(subFolderName) 22 | } 23 | const file = subFolder.createFile(blob) 24 | return ContentService.createTextOutput('Done') 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Robert Sasak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text](esp32cam-google-drive.png 'ESP32 cam + Google Drive') 2 | 3 | # esp32cam-google-drive 4 | 5 | Upload photos from ESP32 camera to Google Drive folder 6 | 7 | ## Problem 8 | 9 | ESP32 is an awesome piece of hardware. It would be awesome to use it as a timelapse camera. Instead of saving files to the SD card, one might want to upload pictures. But where? Google drive is perfect for such a purpose. 10 | 11 | Google scripts do not support direct uploading of binary files. Photo is split into chunks which are converted to base64 and uploaded as text. 12 | 13 | ## Two parts 14 | 15 | Solution consists of two parts. Arduino code and Google script. 16 | 17 | ### ESP32 camera code 18 | 19 | Open file _config.h_ in Arduino and edit your wifi network SSID name and password as well as _scriptId_ which you will get once you upload _esp32.gs_ file into https://script.google.com/ . 20 | 21 | ### Google script code 22 | 23 | Create a new project at https://script.google.com/ and copy there content of _esp32.gs_. Publish your newly created script by Publish > Deploy as web app. You will need to give it permission as asked. 24 | 25 | ## Inspered by 26 | 27 | This repo is build on the top of amazing work of https://github.com/gsampallo/esp32cam-gdrive . However gsampallo solution worked only with small photos. 28 | 29 | ## OTA 30 | 31 | Over the air update was added for convenience. _AI-Thinker ESP32-CAM_ do not have option for choosing partition scheme in Arduino editor. By default it only offers _Huge App_. If you want to use OTA you need to add a custom _build.local.txt_ file into _Arduino15\packages\esp32\hardware\esp32\1.x.x\boards.txt_ as stated [here](https://arduino.stackexchange.com/questions/75198/why-doesnt-ota-work-with-the-ai-thinker-esp32-cam-board). Restart Arduino and select _Minimal SPIFFS_ partition scheme. 32 | 33 | ``` 34 | esp32cam.menu.PartitionScheme.huge_app=Huge APP (3MB No OTA/1MB SPIFFS) 35 | esp32cam.menu.PartitionScheme.huge_app.build.partitions=huge_app 36 | esp32cam.menu.PartitionScheme.huge_app.upload.maximum_size=3145728 37 | esp32cam.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (Large APPS with OTA) 38 | esp32cam.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs 39 | esp32cam.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080 40 | ``` 41 | -------------------------------------------------------------------------------- /Base64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adam Rudd. 3 | * See LICENSE for more information 4 | * https://github.com/adamvr/arduino-base64 5 | */ 6 | #ifndef _BASE64_H 7 | #define _BASE64_H 8 | 9 | /* b64_alphabet: 10 | * Description: Base64 alphabet table, a mapping between integers 11 | * and base64 digits 12 | * Notes: This is an extern here but is defined in Base64.c 13 | */ 14 | extern const char b64_alphabet[]; 15 | 16 | /* base64_encode: 17 | * Description: 18 | * Encode a string of characters as base64 19 | * Parameters: 20 | * output: the output buffer for the encoding, stores the encoded string 21 | * input: the input buffer for the encoding, stores the binary to be encoded 22 | * inputLen: the length of the input buffer, in bytes 23 | * Return value: 24 | * Returns the length of the encoded string 25 | * Requirements: 26 | * 1. output must not be null or empty 27 | * 2. input must not be null 28 | * 3. inputLen must be greater than or equal to 0 29 | */ 30 | int base64_encode(char *output, char *input, int inputLen); 31 | 32 | /* base64_decode: 33 | * Description: 34 | * Decode a base64 encoded string into bytes 35 | * Parameters: 36 | * output: the output buffer for the decoding, 37 | * stores the decoded binary 38 | * input: the input buffer for the decoding, 39 | * stores the base64 string to be decoded 40 | * inputLen: the length of the input buffer, in bytes 41 | * Return value: 42 | * Returns the length of the decoded string 43 | * Requirements: 44 | * 1. output must not be null or empty 45 | * 2. input must not be null 46 | * 3. inputLen must be greater than or equal to 0 47 | */ 48 | int base64_decode(char *output, char *input, int inputLen); 49 | 50 | /* base64_enc_len: 51 | * Description: 52 | * Returns the length of a base64 encoded string whose decoded 53 | * form is inputLen bytes long 54 | * Parameters: 55 | * inputLen: the length of the decoded string 56 | * Return value: 57 | * The length of a base64 encoded string whose decoded form 58 | * is inputLen bytes long 59 | * Requirements: 60 | * None 61 | */ 62 | int base64_enc_len(int inputLen); 63 | 64 | /* base64_dec_len: 65 | * Description: 66 | * Returns the length of the decoded form of a 67 | * base64 encoded string 68 | * Parameters: 69 | * input: the base64 encoded string to be measured 70 | * inputLen: the length of the base64 encoded string 71 | * Return value: 72 | * Returns the length of the decoded form of a 73 | * base64 encoded string 74 | * Requirements: 75 | * 1. input must not be null 76 | * 2. input must be greater than or equal to zero 77 | */ 78 | int base64_dec_len(char *input, int inputLen); 79 | 80 | #endif // _BASE64_H -------------------------------------------------------------------------------- /Base64.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Adam Rudd. 3 | * See LICENSE for more information 4 | * https://github.com/adamvr/arduino-base64 5 | */ 6 | #if (defined(__AVR__)) 7 | #include 8 | #else 9 | #include 10 | #endif 11 | 12 | const char PROGMEM b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 13 | "abcdefghijklmnopqrstuvwxyz" 14 | "0123456789+/"; 15 | 16 | /* 'Private' declarations */ 17 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3); 18 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4); 19 | inline unsigned char b64_lookup(char c); 20 | 21 | int base64_encode(char *output, char *input, int inputLen) { 22 | int i = 0, j = 0; 23 | int encLen = 0; 24 | unsigned char a3[3]; 25 | unsigned char a4[4]; 26 | 27 | while(inputLen--) { 28 | a3[i++] = *(input++); 29 | if(i == 3) { 30 | a3_to_a4(a4, a3); 31 | 32 | for(i = 0; i < 4; i++) { 33 | output[encLen++] = pgm_read_byte(&b64_alphabet[a4[i]]); 34 | } 35 | 36 | i = 0; 37 | } 38 | } 39 | 40 | if(i) { 41 | for(j = i; j < 3; j++) { 42 | a3[j] = '\0'; 43 | } 44 | 45 | a3_to_a4(a4, a3); 46 | 47 | for(j = 0; j < i + 1; j++) { 48 | output[encLen++] = pgm_read_byte(&b64_alphabet[a4[j]]); 49 | } 50 | 51 | while((i++ < 3)) { 52 | output[encLen++] = '='; 53 | } 54 | } 55 | output[encLen] = '\0'; 56 | return encLen; 57 | } 58 | 59 | int base64_decode(char * output, char * input, int inputLen) { 60 | int i = 0, j = 0; 61 | int decLen = 0; 62 | unsigned char a3[3]; 63 | unsigned char a4[4]; 64 | 65 | 66 | while (inputLen--) { 67 | if(*input == '=') { 68 | break; 69 | } 70 | 71 | a4[i++] = *(input++); 72 | if (i == 4) { 73 | for (i = 0; i <4; i++) { 74 | a4[i] = b64_lookup(a4[i]); 75 | } 76 | 77 | a4_to_a3(a3,a4); 78 | 79 | for (i = 0; i < 3; i++) { 80 | output[decLen++] = a3[i]; 81 | } 82 | i = 0; 83 | } 84 | } 85 | 86 | if (i) { 87 | for (j = i; j < 4; j++) { 88 | a4[j] = '\0'; 89 | } 90 | 91 | for (j = 0; j <4; j++) { 92 | a4[j] = b64_lookup(a4[j]); 93 | } 94 | 95 | a4_to_a3(a3,a4); 96 | 97 | for (j = 0; j < i - 1; j++) { 98 | output[decLen++] = a3[j]; 99 | } 100 | } 101 | output[decLen] = '\0'; 102 | return decLen; 103 | } 104 | 105 | int base64_enc_len(int plainLen) { 106 | int n = plainLen; 107 | return (n + 2 - ((n + 2) % 3)) / 3 * 4; 108 | } 109 | 110 | int base64_dec_len(char * input, int inputLen) { 111 | int i = 0; 112 | int numEq = 0; 113 | for(i = inputLen - 1; input[i] == '='; i--) { 114 | numEq++; 115 | } 116 | 117 | return ((6 * inputLen) / 8) - numEq; 118 | } 119 | 120 | inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { 121 | a4[0] = (a3[0] & 0xfc) >> 2; 122 | a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); 123 | a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); 124 | a4[3] = (a3[2] & 0x3f); 125 | } 126 | 127 | inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { 128 | a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); 129 | a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); 130 | a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; 131 | } 132 | 133 | inline unsigned char b64_lookup(char c) { 134 | if(c >='A' && c <='Z') return c - 'A'; 135 | if(c >='a' && c <='z') return c - 71; 136 | if(c >='0' && c <='9') return c + 4; 137 | if(c == '+') return 62; 138 | if(c == '/') return 63; 139 | return -1; 140 | } -------------------------------------------------------------------------------- /esp32cam-google-drive.ino: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include "soc/soc.h" 4 | #include "soc/rtc_cntl_reg.h" 5 | #include "Base64.h" 6 | #include "esp_camera.h" 7 | // Time 8 | #include "time.h" 9 | #include "lwip/err.h" 10 | #include "lwip/apps/sntp.h" 11 | // MicroSD 12 | #include "driver/sdmmc_host.h" 13 | #include "driver/sdmmc_defs.h" 14 | #include "sdmmc_cmd.h" 15 | #include "esp_vfs_fat.h" 16 | // 17 | #include 18 | // OTA 19 | #include 20 | #include 21 | 22 | const char *folder = FOLDER; 23 | const char *scriptID = SCRIPT_ID; 24 | const char *wifiNetwork = WIFI_NETWORK; 25 | const char *password = PASSWORD; 26 | const char *host = HOST; 27 | const int port = PORT; 28 | const int sleepTime = SLEEP_TIME; 29 | const int sleepTimeNight = SLEEP_TIME_NIGHT; 30 | const int nightStart = NIGHT_START; 31 | const int nightEnd = NIGHT_END; 32 | const int waitingTime = WAITING_TIME; 33 | const int otaWaitingTime = OTA_WAITING_TIME; 34 | 35 | #define CAMERA_MODEL_AI_THINKER 36 | 37 | #define PWDN_GPIO_NUM 32 38 | #define RESET_GPIO_NUM -1 39 | #define XCLK_GPIO_NUM 0 40 | #define SIOD_GPIO_NUM 26 41 | #define SIOC_GPIO_NUM 27 42 | 43 | #define Y9_GPIO_NUM 35 44 | #define Y8_GPIO_NUM 34 45 | #define Y7_GPIO_NUM 39 46 | #define Y6_GPIO_NUM 36 47 | #define Y5_GPIO_NUM 21 48 | #define Y4_GPIO_NUM 19 49 | #define Y3_GPIO_NUM 18 50 | #define Y2_GPIO_NUM 5 51 | #define VSYNC_GPIO_NUM 25 52 | #define HREF_GPIO_NUM 23 53 | #define PCLK_GPIO_NUM 22 54 | 55 | bool isNight = false; 56 | 57 | bool connectWifi() 58 | { 59 | Serial.println("Sending to " + String(FOLDER)); 60 | WiFi.mode(WIFI_STA); 61 | 62 | Serial.println(""); 63 | Serial.print("Connecting to "); 64 | Serial.println(wifiNetwork); 65 | WiFi.begin(wifiNetwork, password); 66 | int retry = 20; 67 | while (WiFi.status() != WL_CONNECTED && retry > 0) 68 | { 69 | Serial.print("."); 70 | delay(500); 71 | retry--; 72 | } 73 | if (WiFi.status() == WL_CONNECTED) { 74 | IPAddress ip = WiFi.localIP(); 75 | Serial.println(ip); 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | void setup() 82 | { 83 | WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); 84 | Serial.begin(115200); 85 | 86 | if (connectWifi()) 87 | { 88 | ota(); 89 | } 90 | 91 | camera_fb_t *fb = getPicture(); 92 | 93 | if (WiFi.status() == WL_CONNECTED) 94 | { 95 | initTime(); 96 | sendPhotoDrive(fb); 97 | } 98 | else 99 | { 100 | Serial.println("Cannot connect to Wifi"); 101 | } 102 | 103 | esp_err_t err = initSDCard(); 104 | if (err == ESP_OK) 105 | { 106 | Serial.println("SD card mount successfully!"); 107 | savePhoto(fb); 108 | } 109 | else 110 | { 111 | Serial.printf("Failed to mount SD card VFAT filesystem. Error: %s\n", esp_err_to_name(err)); 112 | } 113 | esp_camera_fb_return(fb); 114 | deepSleep(); 115 | } 116 | 117 | void loop() 118 | { 119 | } 120 | 121 | esp_err_t initCamera() 122 | { 123 | camera_config_t config; 124 | config.ledc_channel = LEDC_CHANNEL_0; 125 | config.ledc_timer = LEDC_TIMER_0; 126 | config.pin_d0 = Y2_GPIO_NUM; 127 | config.pin_d1 = Y3_GPIO_NUM; 128 | config.pin_d2 = Y4_GPIO_NUM; 129 | config.pin_d3 = Y5_GPIO_NUM; 130 | config.pin_d4 = Y6_GPIO_NUM; 131 | config.pin_d5 = Y7_GPIO_NUM; 132 | config.pin_d6 = Y8_GPIO_NUM; 133 | config.pin_d7 = Y9_GPIO_NUM; 134 | config.pin_xclk = XCLK_GPIO_NUM; 135 | config.pin_pclk = PCLK_GPIO_NUM; 136 | config.pin_vsync = VSYNC_GPIO_NUM; 137 | config.pin_href = HREF_GPIO_NUM; 138 | config.pin_sscb_sda = SIOD_GPIO_NUM; 139 | config.pin_sscb_scl = SIOC_GPIO_NUM; 140 | config.pin_pwdn = PWDN_GPIO_NUM; 141 | config.pin_reset = RESET_GPIO_NUM; 142 | config.xclk_freq_hz = 20000000; 143 | config.pixel_format = PIXFORMAT_JPEG; 144 | if (psramFound()) 145 | { 146 | Serial.println("PSRAM found"); 147 | config.frame_size = FRAMESIZE_UXGA; 148 | config.jpeg_quality = 10; 149 | config.fb_count = 2; 150 | } 151 | else 152 | { 153 | config.frame_size = FRAMESIZE_SVGA; 154 | config.jpeg_quality = 12; 155 | config.fb_count = 1; 156 | } 157 | 158 | return esp_camera_init(&config); 159 | } 160 | 161 | void sendPhotoDrive(camera_fb_t *fb) 162 | { 163 | Serial.println("Connect to " + String(host)); 164 | WiFiClientSecure client; 165 | client.setInsecure(); // Comment out this line on Windows 166 | if (client.connect(host, port)) 167 | { 168 | Serial.println("Connection successful"); 169 | Serial.println("Sending image to Google Drive."); 170 | Serial.println("Size: " + String(fb->len) + "byte"); 171 | 172 | String url = "/macros/s/" + String(scriptID) + "/exec?folder=" + String(folder); 173 | 174 | client.println("POST " + url + " HTTP/1.1"); 175 | client.println("Host: " + String(host)); 176 | client.println("Transfer-Encoding: chunked"); 177 | client.println(); 178 | 179 | int fbLen = fb->len; 180 | char *input = (char *)fb->buf; 181 | int chunkSize = 3 * 1000; // must be multiple of 3 182 | int chunkBase64Size = base64_enc_len(chunkSize); 183 | char output[chunkBase64Size + 1]; 184 | 185 | Serial.println(); 186 | int chunk = 0; 187 | for (int i = 0; i < fbLen; i += chunkSize) 188 | { 189 | int l = base64_encode(output, input, min(fbLen - i, chunkSize)); 190 | client.print(l, HEX); 191 | client.print("\r\n"); 192 | client.print(output); 193 | client.print("\r\n"); 194 | delay(100); 195 | input += chunkSize; 196 | Serial.print("."); 197 | chunk++; 198 | if (chunk % 50 == 0) 199 | { 200 | Serial.println(); 201 | } 202 | } 203 | client.print("0\r\n"); 204 | client.print("\r\n"); 205 | 206 | Serial.println("Waiting for response."); 207 | long int StartTime = millis(); 208 | while (!client.available()) 209 | { 210 | Serial.print("."); 211 | delay(100); 212 | if ((StartTime + waitingTime * 1000) < millis()) 213 | { 214 | Serial.println(); 215 | Serial.println("No response."); 216 | break; 217 | } 218 | } 219 | Serial.println(); 220 | while (client.available()) 221 | { 222 | Serial.print(char(client.read())); 223 | } 224 | } 225 | else 226 | { 227 | Serial.println("Connected to " + String(host) + " failed."); 228 | } 229 | client.stop(); 230 | } 231 | 232 | void initTime() 233 | { 234 | sntp_setoperatingmode(SNTP_OPMODE_POLL); 235 | sntp_setservername(0, "pool.ntp.org"); 236 | sntp_init(); 237 | // wait for time to be set 238 | time_t now = 0; 239 | struct tm timeinfo; 240 | timeinfo = {0}; 241 | int retry = 0; 242 | const int retry_count = 10; 243 | while (timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) 244 | { 245 | Serial.printf("Waiting for system time to be set... (%d/%d)\n", retry, retry_count); 246 | delay(2000); 247 | time(&now); 248 | localtime_r(&now, &timeinfo); 249 | } 250 | setenv("TZ", "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", 1); 251 | tzset(); 252 | 253 | if (timeinfo.tm_hour >= nightStart || timeinfo.tm_hour < nightEnd) 254 | { 255 | isNight = true; 256 | } 257 | } 258 | 259 | static esp_err_t initSDCard() 260 | { 261 | esp_err_t ret = ESP_FAIL; 262 | sdmmc_host_t host = SDMMC_HOST_DEFAULT(); 263 | sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); 264 | esp_vfs_fat_sdmmc_mount_config_t mount_config = { 265 | .format_if_mount_failed = false, 266 | .max_files = 1, 267 | }; 268 | sdmmc_card_t *card; 269 | 270 | Serial.println("Mounting SD card..."); 271 | return esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); 272 | } 273 | 274 | static esp_err_t savePhotoToSDCard(char *filename, camera_fb_t *fb) 275 | { 276 | Serial.println(filename); 277 | char *path = (char *)malloc(150); 278 | sprintf(path, "/sdcard/%s", folder); 279 | mkdir(path, 0777); 280 | free(path); 281 | FILE *file = fopen(filename, "w"); 282 | if (file != NULL) 283 | { 284 | size_t err = fwrite(fb->buf, 1, fb->len, file); 285 | Serial.printf("File saved: %s\n", filename); 286 | } 287 | else 288 | { 289 | Serial.println("Could not open file"); 290 | } 291 | fclose(file); 292 | esp_camera_fb_return(fb); 293 | } 294 | 295 | void writeIntIntoEEPROM(int address, int number) 296 | { 297 | EEPROM.write(address, number >> 8); 298 | EEPROM.write(address + 1, number & 0xFF); 299 | } 300 | 301 | int readIntFromEEPROM(int address) 302 | { 303 | byte byte1 = EEPROM.read(address); 304 | byte byte2 = EEPROM.read(address + 1); 305 | return (byte1 << 8) + byte2; 306 | } 307 | 308 | void savePhoto(camera_fb_t *fb) 309 | { 310 | char *filename = (char *)malloc(150); 311 | struct tm timeinfo; 312 | time_t now; 313 | time(&now); 314 | localtime_r(&now, &timeinfo); 315 | if (timeinfo.tm_year < (2016 - 1900)) 316 | { // if no internet or time not set 317 | esp_sleep_wakeup_cause_t wakeup_reason; 318 | wakeup_reason = esp_sleep_get_wakeup_cause(); 319 | int randomNumber; 320 | int fileNumber = 1; 321 | EEPROM.begin(4); 322 | if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) 323 | { 324 | randomNumber = readIntFromEEPROM(0); 325 | fileNumber = readIntFromEEPROM(2); 326 | fileNumber++; 327 | writeIntIntoEEPROM(2, fileNumber); 328 | } 329 | else 330 | { 331 | randomNumber = random(10000, 99999); 332 | writeIntIntoEEPROM(0, randomNumber); 333 | writeIntIntoEEPROM(2, fileNumber); 334 | } 335 | if (!EEPROM.commit()) 336 | { 337 | Serial.println("ERROR! Data commit failed"); 338 | } 339 | sprintf(filename, "/sdcard/%s/%d-%05d.jpg", folder, randomNumber, fileNumber); 340 | } 341 | else 342 | { 343 | char strftime_buf[64]; 344 | strftime(strftime_buf, sizeof(strftime_buf), "%F_%H_%M_%S", &timeinfo); 345 | sprintf(filename, "/sdcard/%s/%s.jpg", folder, strftime_buf); 346 | } 347 | savePhotoToSDCard(filename, fb); 348 | free(filename); 349 | } 350 | 351 | void deepSleep() 352 | { 353 | uint64_t sleep = sleepTime; 354 | if (isNight) 355 | { 356 | sleep = sleepTimeNight; 357 | } 358 | Serial.println("Deep sleep for " + String(sleep) + " seconds. Good night."); 359 | Serial.flush(); 360 | esp_sleep_enable_timer_wakeup(sleep * 1000000); 361 | esp_deep_sleep_start(); 362 | } 363 | 364 | void ota() 365 | { 366 | if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) 367 | { 368 | return; 369 | } 370 | Serial.println("Waiting for update from Arduino editor."); 371 | ArduinoOTA.setHostname(folder); 372 | ArduinoOTA 373 | .onStart([]() { 374 | String type; 375 | if (ArduinoOTA.getCommand() == U_FLASH) 376 | type = "sketch"; 377 | else // U_SPIFFS 378 | type = "filesystem"; 379 | 380 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 381 | Serial.println("Start updating " + type); 382 | }) 383 | .onEnd([]() { 384 | Serial.println("\nEnd"); 385 | }) 386 | .onProgress([](unsigned int progress, unsigned int total) { 387 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); 388 | }) 389 | .onError([](ota_error_t error) { 390 | Serial.printf("Error[%u]: ", error); 391 | if (error == OTA_AUTH_ERROR) 392 | Serial.println("Auth Failed"); 393 | else if (error == OTA_BEGIN_ERROR) 394 | Serial.println("Begin Failed"); 395 | else if (error == OTA_CONNECT_ERROR) 396 | Serial.println("Connect Failed"); 397 | else if (error == OTA_RECEIVE_ERROR) 398 | Serial.println("Receive Failed"); 399 | else if (error == OTA_END_ERROR) 400 | Serial.println("End Failed"); 401 | }); 402 | 403 | ArduinoOTA.begin(); 404 | unsigned long start = millis(); 405 | while (millis() - start <= otaWaitingTime * 1000) 406 | { 407 | ArduinoOTA.handle(); 408 | } 409 | } 410 | 411 | camera_fb_t *getPicture() 412 | { 413 | esp_err_t err = initCamera(); 414 | if (err != ESP_OK) 415 | { 416 | Serial.printf("Camera init failed with error 0x%x", err); 417 | deepSleep(); 418 | } 419 | sensor_t * s = esp_camera_sensor_get(); 420 | if (FLIP) 421 | { 422 | s->set_hmirror(s, 1); 423 | s->set_vflip(s, 1); 424 | } 425 | delay(4000); 426 | camera_fb_t *fb = esp_camera_fb_get(); 427 | if (!fb) 428 | { 429 | Serial.println("Camera capture failed, check if camera is connected"); 430 | deepSleep(); 431 | } 432 | return fb; 433 | } --------------------------------------------------------------------------------