├── .github └── FUNDING.yml ├── README.md ├── timelapse-javascript.ino ├── timelapse-sd.ino └── timelapse-server.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: robotzero # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32cam-timelapse 2 | Create time-lapse images using the ESP32-CAM and other ESP32 based camera modules 3 | 4 | There are three sketches: 5 | 6 | **timelapse-javascript.ino** display captured photos in the browser and use JavaScript to save them. 7 | 8 | **timelapse-sd.ino** capture and write photos to a microSD card reader. 9 | 10 | **timelapse-server.ino** capture and send photos to a server on the internet. 11 | 12 | More information on my blog here: https://robotzero.one/time-lapse-esp32-cameras/ 13 | -------------------------------------------------------------------------------- /timelapse-javascript.ino: -------------------------------------------------------------------------------- 1 | #include "esp_http_server.h" 2 | #include "esp_camera.h" 3 | #include 4 | #include "Arduino.h" 5 | 6 | const char* ssid = "NSA"; 7 | const char* password = "orange"; 8 | 9 | //Select a sensible image size for image to avoid overloading camera/wifi etc 10 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_QQVGA // 160x120 11 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_QQVGA2 // 128x160 12 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_QCIF // 176x144 13 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_HQVGA // 240x176 14 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_QVGA // 320x240 15 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_CIF // 400x296 16 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_VGA // 640x480 17 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_SVGA // 800x600 18 | #define TIMELAPSE_FRAMESIZE FRAMESIZE_XGA // 1024x768 19 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_SXGA // 1280x1024 20 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_UXGA // 1600x1200 21 | //#define TIMELAPSE_FRAMESIZE FRAMESIZE_QXGA // 2048*1536 22 | 23 | 24 | #define index_html_gz_len 3663 25 | // view source in browser to see HTML. See Editing ESP32 HTML tutorial for how to edit. 26 | const uint8_t index_html_gz[] = { 27 | 0x1f,0x8b,0x08,0x00,0x97,0xa1,0xb9,0x5c,0x00,0xff,0x7d,0x54,0x61,0x8f,0xe2,0x36,0x10,0xfd,0x4c,0x7e,0xc5,0x34,0x3a,0x29,0x49,0x0f,0x08,0x5c,0x45,0x55,0xdd,0x26,0xa9,0xda,0x05,0xb5,0x54,0xcb,0xde,0xa9,0xcb,0xb7,0xaa,0xea,0x79,0xed,0x21,0xb8,0xe7,0xd8,0x69,0xec,0x84,0xa2,0x15,0xff,0xfd,0xc6,0x49,0x38,0xd1,0xaa,0x57,0x21,0x11,0x87,0x79,0x7e,0xf3,0x66,0xe6,0x0d,0xd9,0x57,0xc2,0x70,0x77,0xae,0x11,0x8e,0xae,0x52,0x45,0x90,0x5d,0x1f,0xc8,0x04,0x3d,0x2a,0x74,0x0c,0xf8,0x91,0x35,0x16,0x5d,0x1e,0xb6,0xee,0x30,0xfb,0x2e,0xbc,0xfe,0xac,0x59,0x85,0x79,0xd8,0x49,0x3c,0xd5,0xa6,0x71,0x21,0x70,0xa3,0x1d,0x6a,0x82,0x9d,0xa4,0x70,0xc7,0x5c,0x60,0x27,0x39,0xce,0xfa,0x97,0xa9,0xd4,0xd2,0x49,0xa6,0x66,0x96,0x33,0x85,0xf9,0xd2,0x73,0x38,0xe9,0x14,0x16,0xbf,0xb0,0x8e,0x3d,0xf1,0x46,0xd6,0x0e,0xb6,0x15,0x2b,0xd1,0x66,0xe9,0x10,0x08,0xb2,0x74,0xd4,0xf0,0x6c,0xc4,0xb9,0x08,0x7e,0xdd,0xdc,0xbf,0xdb,0xed,0x36,0x8f,0xeb,0xcd,0x1a,0x9e,0x36,0xfb,0xfd,0xf6,0xf1,0xa7,0xa7,0xb7,0xd9,0x73,0x53,0x04,0x6f,0xc0,0x22,0xe5,0x16,0x16,0x66,0xf0,0x1e,0x4d,0xad,0x10,0x4e,0x4c,0x7d,0x94,0xba,0x04,0x61,0x4e,0x1a,0xdc,0x11,0xc1,0xba,0x06,0xd1,0xf5,0xf0,0x6f,0x6e,0xe0,0xf7,0xca,0xb4,0x74,0xa8,0x4c,0xe7,0xd1,0x1d,0x36,0x67,0x38,0x30,0x3b,0xe0,0x56,0x5f,0xc4,0x69,0xd3,0x54,0x4c,0xa9,0x73,0x0f,0x5b,0x2e,0xfe,0x9f,0xcf,0x2a,0x73,0x1a,0xa1,0x6f,0x6e,0xa1,0x8f,0xb2,0x3c,0x3a,0x50,0x8c,0xde,0x38,0xab,0xd1,0x4e,0x49,0x23,0xb5,0x79,0x4a,0x97,0x8d,0x1e,0x84,0xde,0xc2,0xdf,0x33,0x77,0x04,0x73,0x18,0x8a,0x69,0x35,0x18,0x0d,0x34,0x18,0x85,0xac,0x01,0xc1,0x06,0xfa,0xfe,0x6b,0xcf,0x3e,0x22,0x45,0x6a,0xc9,0x5d,0xdb,0x20,0xa0,0xd7,0xf0,0x16,0x32,0x5b,0x33,0x0d,0x52,0xe4,0xe1,0xc8,0x18,0x16,0xab,0x2c,0xf5,0x3f,0x16,0xd7,0x1c,0x03,0x85,0xd4,0x75,0xeb,0xc0,0xfb,0x21,0x0f,0x1b,0xa6,0x4b,0x0c,0xa1,0x92,0x3a,0x0f,0x97,0xf4,0x64,0x7f,0xe7,0xe1,0xb7,0x8b,0x10,0x3a,0xa6,0x5a,0x0a,0xaf,0x42,0x48,0xe9,0x86,0x90,0x5d,0x4f,0x2c,0xfd,0xf4,0x66,0xde,0x03,0x4c,0x6a,0x6c,0xc2,0x22,0x4b,0x29,0x44,0x00,0xdb,0x4f,0xb7,0x08,0xc8,0x67,0x6d,0x45,0xfe,0x98,0x33,0x21,0x36,0x1d,0x1d,0x1e,0xa4,0x25,0xbf,0x60,0x13,0x47,0xeb,0x77,0xbb,0xfb,0xc1,0x3c,0x0f,0x86,0x09,0x14,0xd1,0x14,0x0e,0xad,0xe6,0x4e,0x1a,0x1d,0x27,0xf0,0x12,0x4c,0x3a,0x2a,0x93,0x43,0x4e,0xf3,0x1c,0x39,0x94,0xe1,0xcc,0x87,0xe7,0xa6,0x91,0xa5,0xd4,0x77,0xc1,0x84,0x32,0x5b,0x07,0xf2,0x1f,0xa8,0x12,0xdd,0x46,0xa1,0x3f,0xfe,0x78,0xde,0x8a,0x38,0xfa,0x97,0xc6,0x28,0xb9,0x9b,0x04,0xf4,0xf1,0xec,0x4e,0x52,0x9d,0x25,0x5d,0x5e,0x2d,0x16,0x8b,0xbb,0x20,0x98,0x5c,0x05,0x80,0x32,0xa6,0x1e,0x54,0x00,0xd1,0xcf,0xa5,0xb6,0xd8,0xb8,0x1f,0xc4,0x9f,0x8c,0x13,0xef,0xcf,0xfb,0xdd,0x43,0x1c,0x3d,0xe3,0xc1,0x90,0xbf,0x34,0x09,0x8f,0x32,0x59,0x95,0x60,0x1b,0x9e,0x87,0xd1,0xeb,0x0f,0xaf,0x5e,0xf8,0x25,0xa5,0xf1,0xfa,0x59,0x7c,0xff,0x07,0x7f,0xce,0x5f,0xbd,0xac,0x99,0xc3,0xb9,0x36,0xa7,0x38,0xb9,0x7c,0x78,0x1d,0x85,0x45,0x94,0x78,0xe2,0x93,0xd4,0xe4,0xd5,0x39,0x6d,0xd9,0x5e,0x56,0x68,0x5a,0x17,0xfb,0xb4,0xd3,0x51,0x56,0x42,0xf5,0x5d,0xbc,0xd2,0xcf,0x95,0xfd,0xd5,0xd2,0x5c,0x9f,0x50,0x21,0x77,0x86,0x1a,0xd8,0x8f,0xed,0xb7,0xdb,0xb1,0xfd,0x1e,0x25,0xff,0xd1,0x68,0xda,0x64,0x0a,0xde,0xb4,0x17,0x62,0x1c,0x4b,0xfb,0xdc,0x80,0xda,0x2f,0xfb,0x56,0xbb,0xd8,0x1d,0xa5,0x9d,0xf7,0xc3,0x4e,0xbe,0x5e,0xf6,0x5d,0x21,0xd8,0x17,0x7b,0x3b,0xda,0x88,0xd2,0x4a,0x4d,0xb9,0x7c,0x63,0x88,0x6c,0x60,0x4d,0xc7,0xeb,0x17,0x5f,0x48,0x30,0x19,0x3a,0x7a,0x17,0x5c,0x12,0x5a,0xf2,0xab,0x3d,0xb2,0x74,0xd8,0x73,0x5a,0x7b,0xff,0x0f,0xf4,0x09,0xe7,0x37,0xcb,0x1c,0x98,0x04,0x00,0x00 28 | }; 29 | 30 | httpd_handle_t camera_httpd = NULL; 31 | 32 | char filename[40]; 33 | int file_number = 0; 34 | 35 | // CAMERA_MODEL_AI_THINKER 36 | #define PWDN_GPIO_NUM 32 37 | #define RESET_GPIO_NUM -1 38 | #define XCLK_GPIO_NUM 0 39 | #define SIOD_GPIO_NUM 26 40 | #define SIOC_GPIO_NUM 27 41 | 42 | #define Y9_GPIO_NUM 35 43 | #define Y8_GPIO_NUM 34 44 | #define Y7_GPIO_NUM 39 45 | #define Y6_GPIO_NUM 36 46 | #define Y5_GPIO_NUM 21 47 | #define Y4_GPIO_NUM 19 48 | #define Y3_GPIO_NUM 18 49 | #define Y2_GPIO_NUM 5 50 | #define VSYNC_GPIO_NUM 25 51 | #define HREF_GPIO_NUM 23 52 | #define PCLK_GPIO_NUM 22 53 | 54 | void startCameraServer(); 55 | 56 | void setup() { 57 | Serial.begin(115200); 58 | Serial.setDebugOutput(true); 59 | Serial.println(); 60 | 61 | camera_config_t config; 62 | config.ledc_channel = LEDC_CHANNEL_0; 63 | config.ledc_timer = LEDC_TIMER_0; 64 | config.pin_d0 = Y2_GPIO_NUM; 65 | config.pin_d1 = Y3_GPIO_NUM; 66 | config.pin_d2 = Y4_GPIO_NUM; 67 | config.pin_d3 = Y5_GPIO_NUM; 68 | config.pin_d4 = Y6_GPIO_NUM; 69 | config.pin_d5 = Y7_GPIO_NUM; 70 | config.pin_d6 = Y8_GPIO_NUM; 71 | config.pin_d7 = Y9_GPIO_NUM; 72 | config.pin_xclk = XCLK_GPIO_NUM; 73 | config.pin_pclk = PCLK_GPIO_NUM; 74 | config.pin_vsync = VSYNC_GPIO_NUM; 75 | config.pin_href = HREF_GPIO_NUM; 76 | config.pin_sscb_sda = SIOD_GPIO_NUM; 77 | config.pin_sscb_scl = SIOC_GPIO_NUM; 78 | config.pin_pwdn = PWDN_GPIO_NUM; 79 | config.pin_reset = RESET_GPIO_NUM; 80 | config.xclk_freq_hz = 20000000; 81 | config.pixel_format = PIXFORMAT_JPEG; 82 | //init with high specs to pre-allocate larger buffers 83 | if (psramFound()) { 84 | config.frame_size = FRAMESIZE_UXGA; 85 | config.jpeg_quality = 10; 86 | config.fb_count = 2; 87 | } else { 88 | config.frame_size = FRAMESIZE_SVGA; 89 | config.jpeg_quality = 12; 90 | config.fb_count = 1; 91 | } 92 | 93 | // camera init 94 | esp_err_t err = esp_camera_init(&config); 95 | if (err != ESP_OK) { 96 | Serial.printf("Camera init failed with error 0x%x", err); 97 | return; 98 | } 99 | 100 | //drop down frame size for shorter interval capture 101 | sensor_t * s = esp_camera_sensor_get(); 102 | s->set_framesize(s, TIMELAPSE_FRAMESIZE); 103 | 104 | WiFi.begin(ssid, password); 105 | while (WiFi.status() != WL_CONNECTED) { 106 | delay(500); 107 | Serial.print("."); 108 | } 109 | Serial.println(""); 110 | Serial.println("WiFi connected"); 111 | 112 | startCameraServer(); 113 | 114 | Serial.print("Camera Ready! Use 'http://"); 115 | Serial.print(WiFi.localIP()); 116 | Serial.println("' to connect"); 117 | } 118 | 119 | static esp_err_t capture_handler(httpd_req_t *req) { 120 | 121 | camera_fb_t * fb = NULL; 122 | esp_err_t res = ESP_OK; 123 | 124 | fb = esp_camera_fb_get(); 125 | if (!fb) { 126 | Serial.println("Camera capture failed"); 127 | httpd_resp_send_500(req); 128 | return ESP_FAIL; 129 | } 130 | 131 | file_number++; 132 | sprintf(filename, "inline; filename=capture_%d.jpg", file_number); 133 | 134 | httpd_resp_set_type(req, "image/jpeg"); 135 | httpd_resp_set_hdr(req, "Content-Disposition", filename); 136 | 137 | size_t out_len, out_width, out_height; 138 | size_t fb_len = 0; 139 | fb_len = fb->len; 140 | res = httpd_resp_send(req, (const char *)fb->buf, fb->len); 141 | esp_camera_fb_return(fb); 142 | 143 | return res; 144 | } 145 | 146 | static esp_err_t index_handler(httpd_req_t *req) { 147 | httpd_resp_set_type(req, "text/html"); 148 | httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); 149 | return httpd_resp_send(req, (const char *)index_html_gz, index_html_gz_len); 150 | } 151 | 152 | void startCameraServer() { 153 | httpd_config_t config = HTTPD_DEFAULT_CONFIG(); 154 | 155 | httpd_uri_t index_uri = { 156 | .uri = "/", 157 | .method = HTTP_GET, 158 | .handler = index_handler, 159 | .user_ctx = NULL 160 | }; 161 | httpd_uri_t capture_uri = { 162 | .uri = "/capture", 163 | .method = HTTP_GET, 164 | .handler = capture_handler, 165 | .user_ctx = NULL 166 | }; 167 | 168 | if (httpd_start(&camera_httpd, &config) == ESP_OK) { 169 | httpd_register_uri_handler(camera_httpd, &index_uri); 170 | httpd_register_uri_handler(camera_httpd, &capture_uri); 171 | } 172 | } 173 | 174 | void loop() { 175 | // loop not used 176 | delay(10000); 177 | } 178 | -------------------------------------------------------------------------------- /timelapse-sd.ino: -------------------------------------------------------------------------------- 1 | #include "esp_camera.h" 2 | #include 3 | #include "Arduino.h" 4 | // Time 5 | #include "time.h" 6 | #include "lwip/err.h" 7 | #include "lwip/apps/sntp.h" 8 | // MicroSD 9 | #include "driver/sdmmc_host.h" 10 | #include "driver/sdmmc_defs.h" 11 | #include "sdmmc_cmd.h" 12 | #include "esp_vfs_fat.h" 13 | 14 | // Edit ssid, password, capture_interval: 15 | const char* ssid = "NSA"; 16 | const char* password = "orange"; 17 | int capture_interval = 5000; // milliseconds between captures 18 | // 19 | 20 | long current_millis; 21 | long last_capture_millis = 0; 22 | static esp_err_t cam_err; 23 | static esp_err_t card_err; 24 | char strftime_buf[64]; 25 | int file_number = 0; 26 | bool internet_connected = false; 27 | struct tm timeinfo; 28 | time_t now; 29 | 30 | 31 | // CAMERA_MODEL_AI_THINKER 32 | #define PWDN_GPIO_NUM 32 33 | #define RESET_GPIO_NUM -1 34 | #define XCLK_GPIO_NUM 0 35 | #define SIOD_GPIO_NUM 26 36 | #define SIOC_GPIO_NUM 27 37 | #define Y9_GPIO_NUM 35 38 | #define Y8_GPIO_NUM 34 39 | #define Y7_GPIO_NUM 39 40 | #define Y6_GPIO_NUM 36 41 | #define Y5_GPIO_NUM 21 42 | #define Y4_GPIO_NUM 19 43 | #define Y3_GPIO_NUM 18 44 | #define Y2_GPIO_NUM 5 45 | #define VSYNC_GPIO_NUM 25 46 | #define HREF_GPIO_NUM 23 47 | #define PCLK_GPIO_NUM 22 48 | 49 | void setup() { 50 | Serial.begin(115200); 51 | 52 | if (init_wifi()) { // Connected to WiFi 53 | internet_connected = true; 54 | Serial.println("Internet connected"); 55 | init_time(); 56 | time(&now); 57 | setenv("TZ", "GMT0BST,M3.5.0/01,M10.5.0/02", 1); 58 | tzset(); 59 | } 60 | 61 | camera_config_t config; 62 | config.ledc_channel = LEDC_CHANNEL_0; 63 | config.ledc_timer = LEDC_TIMER_0; 64 | config.pin_d0 = Y2_GPIO_NUM; 65 | config.pin_d1 = Y3_GPIO_NUM; 66 | config.pin_d2 = Y4_GPIO_NUM; 67 | config.pin_d3 = Y5_GPIO_NUM; 68 | config.pin_d4 = Y6_GPIO_NUM; 69 | config.pin_d5 = Y7_GPIO_NUM; 70 | config.pin_d6 = Y8_GPIO_NUM; 71 | config.pin_d7 = Y9_GPIO_NUM; 72 | config.pin_xclk = XCLK_GPIO_NUM; 73 | config.pin_pclk = PCLK_GPIO_NUM; 74 | config.pin_vsync = VSYNC_GPIO_NUM; 75 | config.pin_href = HREF_GPIO_NUM; 76 | config.pin_sscb_sda = SIOD_GPIO_NUM; 77 | config.pin_sscb_scl = SIOC_GPIO_NUM; 78 | config.pin_pwdn = PWDN_GPIO_NUM; 79 | config.pin_reset = RESET_GPIO_NUM; 80 | config.xclk_freq_hz = 20000000; 81 | config.pixel_format = PIXFORMAT_JPEG; 82 | //init with high specs to pre-allocate larger buffers 83 | if (psramFound()) { 84 | config.frame_size = FRAMESIZE_UXGA; 85 | config.jpeg_quality = 10; 86 | config.fb_count = 2; 87 | } else { 88 | config.frame_size = FRAMESIZE_SVGA; 89 | config.jpeg_quality = 12; 90 | config.fb_count = 1; 91 | } 92 | 93 | // camera init 94 | cam_err = esp_camera_init(&config); 95 | if (cam_err != ESP_OK) { 96 | Serial.printf("Camera init failed with error 0x%x", cam_err); 97 | return; 98 | } 99 | // SD camera init 100 | card_err = init_sdcard(); 101 | if (card_err != ESP_OK) { 102 | Serial.printf("SD Card init failed with error 0x%x", card_err); 103 | return; 104 | } 105 | } 106 | 107 | bool init_wifi() 108 | { 109 | int connAttempts = 0; 110 | Serial.println("\r\nConnecting to: " + String(ssid)); 111 | WiFi.begin(ssid, password); 112 | while (WiFi.status() != WL_CONNECTED ) { 113 | delay(500); 114 | Serial.print("."); 115 | if (connAttempts > 10) return false; 116 | connAttempts++; 117 | } 118 | return true; 119 | } 120 | 121 | void init_time() 122 | { 123 | sntp_setoperatingmode(SNTP_OPMODE_POLL); 124 | sntp_setservername(0, "pool.ntp.org"); 125 | sntp_init(); 126 | // wait for time to be set 127 | time_t now = 0; 128 | timeinfo = { 0 }; 129 | int retry = 0; 130 | const int retry_count = 10; 131 | while (timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) { 132 | Serial.printf("Waiting for system time to be set... (%d/%d)\n", retry, retry_count); 133 | delay(2000); 134 | time(&now); 135 | localtime_r(&now, &timeinfo); 136 | } 137 | } 138 | 139 | static esp_err_t init_sdcard() 140 | { 141 | esp_err_t ret = ESP_FAIL; 142 | sdmmc_host_t host = SDMMC_HOST_DEFAULT(); 143 | sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); 144 | esp_vfs_fat_sdmmc_mount_config_t mount_config = { 145 | .format_if_mount_failed = false, 146 | .max_files = 1, 147 | }; 148 | sdmmc_card_t *card; 149 | 150 | Serial.println("Mounting SD card..."); 151 | ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); 152 | 153 | if (ret == ESP_OK) { 154 | Serial.println("SD card mount successfully!"); 155 | } else { 156 | Serial.printf("Failed to mount SD card VFAT filesystem. Error: %s", esp_err_to_name(ret)); 157 | } 158 | } 159 | 160 | static esp_err_t save_photo_numbered() 161 | { 162 | file_number++; 163 | Serial.print("Taking picture: "); 164 | Serial.print(file_number); 165 | camera_fb_t *fb = esp_camera_fb_get(); 166 | 167 | //char *filename = (char*)malloc(21 + sizeof(int)); 168 | char *filename = (char*)malloc(21 + sizeof(file_number)); 169 | sprintf(filename, "/sdcard/capture_%d.jpg", file_number); 170 | 171 | Serial.println(filename); 172 | FILE *file = fopen(filename, "w"); 173 | if (file != NULL) { 174 | size_t err = fwrite(fb->buf, 1, fb->len, file); 175 | Serial.printf("File saved: %s\n", filename); 176 | } else { 177 | Serial.println("Could not open file"); 178 | } 179 | fclose(file); 180 | esp_camera_fb_return(fb); 181 | free(filename); 182 | } 183 | 184 | static esp_err_t save_photo_dated() 185 | { 186 | Serial.println("Taking picture..."); 187 | camera_fb_t *fb = esp_camera_fb_get(); 188 | 189 | time(&now); 190 | localtime_r(&now, &timeinfo); 191 | strftime(strftime_buf, sizeof(strftime_buf), "%F_%H_%M_%S", &timeinfo); 192 | 193 | char *filename = (char*)malloc(21 + sizeof(strftime_buf)); 194 | sprintf(filename, "/sdcard/capture_%s.jpg", strftime_buf); 195 | 196 | Serial.println(filename); 197 | FILE *file = fopen(filename, "w"); 198 | if (file != NULL) { 199 | size_t err = fwrite(fb->buf, 1, fb->len, file); 200 | Serial.printf("File saved: %s\n", filename); 201 | 202 | } else { 203 | Serial.println("Could not open file"); 204 | } 205 | fclose(file); 206 | esp_camera_fb_return(fb); 207 | free(filename); 208 | } 209 | 210 | void save_photo() 211 | { 212 | if (timeinfo.tm_year < (2016 - 1900) || internet_connected == false) { // if no internet or time not set 213 | save_photo_numbered(); // filenames in numbered order 214 | } else { 215 | save_photo_dated(); // filenames with date and time 216 | } 217 | } 218 | 219 | void loop() 220 | { 221 | current_millis = millis(); 222 | if (current_millis - last_capture_millis > capture_interval) { // Take another picture 223 | last_capture_millis = millis(); 224 | save_photo(); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /timelapse-server.ino: -------------------------------------------------------------------------------- 1 | #include "esp_http_client.h" 2 | #include "esp_camera.h" 3 | #include 4 | #include "Arduino.h" 5 | 6 | const char* ssid = "NSA"; 7 | const char* password = "Orange"; 8 | int capture_interval = 20000; // Microseconds between captures 9 | const char *post_url = "http://your-webserver.net/yourscript.php"; // Location where images are POSTED 10 | 11 | bool internet_connected = false; 12 | long current_millis; 13 | long last_capture_millis = 0; 14 | 15 | // CAMERA_MODEL_AI_THINKER 16 | #define PWDN_GPIO_NUM 32 17 | #define RESET_GPIO_NUM -1 18 | #define XCLK_GPIO_NUM 0 19 | #define SIOD_GPIO_NUM 26 20 | #define SIOC_GPIO_NUM 27 21 | #define Y9_GPIO_NUM 35 22 | #define Y8_GPIO_NUM 34 23 | #define Y7_GPIO_NUM 39 24 | #define Y6_GPIO_NUM 36 25 | #define Y5_GPIO_NUM 21 26 | #define Y4_GPIO_NUM 19 27 | #define Y3_GPIO_NUM 18 28 | #define Y2_GPIO_NUM 5 29 | #define VSYNC_GPIO_NUM 25 30 | #define HREF_GPIO_NUM 23 31 | #define PCLK_GPIO_NUM 22 32 | 33 | void setup() 34 | { 35 | Serial.begin(115200); 36 | 37 | if (init_wifi()) { // Connected to WiFi 38 | internet_connected = true; 39 | Serial.println("Internet connected"); 40 | } 41 | 42 | camera_config_t config; 43 | config.ledc_channel = LEDC_CHANNEL_0; 44 | config.ledc_timer = LEDC_TIMER_0; 45 | config.pin_d0 = Y2_GPIO_NUM; 46 | config.pin_d1 = Y3_GPIO_NUM; 47 | config.pin_d2 = Y4_GPIO_NUM; 48 | config.pin_d3 = Y5_GPIO_NUM; 49 | config.pin_d4 = Y6_GPIO_NUM; 50 | config.pin_d5 = Y7_GPIO_NUM; 51 | config.pin_d6 = Y8_GPIO_NUM; 52 | config.pin_d7 = Y9_GPIO_NUM; 53 | config.pin_xclk = XCLK_GPIO_NUM; 54 | config.pin_pclk = PCLK_GPIO_NUM; 55 | config.pin_vsync = VSYNC_GPIO_NUM; 56 | config.pin_href = HREF_GPIO_NUM; 57 | config.pin_sscb_sda = SIOD_GPIO_NUM; 58 | config.pin_sscb_scl = SIOC_GPIO_NUM; 59 | config.pin_pwdn = PWDN_GPIO_NUM; 60 | config.pin_reset = RESET_GPIO_NUM; 61 | config.xclk_freq_hz = 20000000; 62 | config.pixel_format = PIXFORMAT_JPEG; 63 | //init with high specs to pre-allocate larger buffers 64 | if (psramFound()) { 65 | config.frame_size = FRAMESIZE_UXGA; 66 | config.jpeg_quality = 10; 67 | config.fb_count = 2; 68 | } else { 69 | config.frame_size = FRAMESIZE_SVGA; 70 | config.jpeg_quality = 12; 71 | config.fb_count = 1; 72 | } 73 | 74 | // camera init 75 | esp_err_t err = esp_camera_init(&config); 76 | if (err != ESP_OK) { 77 | Serial.printf("Camera init failed with error 0x%x", err); 78 | return; 79 | } 80 | } 81 | 82 | bool init_wifi() 83 | { 84 | int connAttempts = 0; 85 | Serial.println("\r\nConnecting to: " + String(ssid)); 86 | WiFi.begin(ssid, password); 87 | while (WiFi.status() != WL_CONNECTED ) { 88 | delay(500); 89 | Serial.print("."); 90 | if (connAttempts > 10) return false; 91 | connAttempts++; 92 | } 93 | return true; 94 | } 95 | 96 | 97 | esp_err_t _http_event_handler(esp_http_client_event_t *evt) 98 | { 99 | switch (evt->event_id) { 100 | case HTTP_EVENT_ERROR: 101 | Serial.println("HTTP_EVENT_ERROR"); 102 | break; 103 | case HTTP_EVENT_ON_CONNECTED: 104 | Serial.println("HTTP_EVENT_ON_CONNECTED"); 105 | break; 106 | case HTTP_EVENT_HEADER_SENT: 107 | Serial.println("HTTP_EVENT_HEADER_SENT"); 108 | break; 109 | case HTTP_EVENT_ON_HEADER: 110 | Serial.println(); 111 | Serial.printf("HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); 112 | break; 113 | case HTTP_EVENT_ON_DATA: 114 | Serial.println(); 115 | Serial.printf("HTTP_EVENT_ON_DATA, len=%d", evt->data_len); 116 | if (!esp_http_client_is_chunked_response(evt->client)) { 117 | // Write out data 118 | // printf("%.*s", evt->data_len, (char*)evt->data); 119 | } 120 | break; 121 | case HTTP_EVENT_ON_FINISH: 122 | Serial.println(""); 123 | Serial.println("HTTP_EVENT_ON_FINISH"); 124 | break; 125 | case HTTP_EVENT_DISCONNECTED: 126 | Serial.println("HTTP_EVENT_DISCONNECTED"); 127 | break; 128 | } 129 | return ESP_OK; 130 | } 131 | 132 | static esp_err_t take_send_photo() 133 | { 134 | Serial.println("Taking picture..."); 135 | camera_fb_t * fb = NULL; 136 | esp_err_t res = ESP_OK; 137 | 138 | fb = esp_camera_fb_get(); 139 | if (!fb) { 140 | Serial.println("Camera capture failed"); 141 | return ESP_FAIL; 142 | } 143 | 144 | esp_http_client_handle_t http_client; 145 | 146 | esp_http_client_config_t config_client = {0}; 147 | config_client.url = post_url; 148 | config_client.event_handler = _http_event_handler; 149 | config_client.method = HTTP_METHOD_POST; 150 | 151 | http_client = esp_http_client_init(&config_client); 152 | 153 | esp_http_client_set_post_field(http_client, (const char *)fb->buf, fb->len); 154 | 155 | esp_http_client_set_header(http_client, "Content-Type", "image/jpg"); 156 | 157 | esp_err_t err = esp_http_client_perform(http_client); 158 | if (err == ESP_OK) { 159 | Serial.print("esp_http_client_get_status_code: "); 160 | Serial.println(esp_http_client_get_status_code(http_client)); 161 | } 162 | 163 | esp_http_client_cleanup(http_client); 164 | 165 | esp_camera_fb_return(fb); 166 | } 167 | 168 | 169 | 170 | void loop() 171 | { 172 | // TODO check Wifi and reconnect if needed 173 | 174 | current_millis = millis(); 175 | if (current_millis - last_capture_millis > capture_interval) { // Take another picture 176 | last_capture_millis = millis(); 177 | take_send_photo(); 178 | } 179 | } 180 | --------------------------------------------------------------------------------