1 | // Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
2 | //
|
3 | // Licensed under the Apache License, Version 2.0 (the "License");
|
4 | // you may not use this file except in compliance with the License.
|
5 | // You may obtain a copy of the License at
|
6 | //
|
7 | // http://www.apache.org/licenses/LICENSE-2.0
|
8 | //
|
9 | // Unless required by applicable law or agreed to in writing, software
|
10 | // distributed under the License is distributed on an "AS IS" BASIS,
|
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 | // See the License for the specific language governing permissions and
|
13 | // limitations under the License.
|
14 |
|
15 |
|
16 | #include <stdio.h>
|
17 | #include <stdlib.h>
|
18 | #include <string.h>
|
19 |
|
20 | #include "freertos/FreeRTOS.h"
|
21 | #include "freertos/task.h"
|
22 | #include "freertos/semphr.h"
|
23 | #include "freertos/event_groups.h"
|
24 |
|
25 | #include "esp_system.h"
|
26 | #include "esp_wifi.h"
|
27 | #include "esp_event_loop.h"
|
28 | #include "esp_log.h"
|
29 | #include "esp_err.h"
|
30 | #include "nvs_flash.h"
|
31 |
|
32 | #include "driver/gpio.h"
|
33 |
|
34 | extern "C" {
|
35 | #include "camera.h"
|
36 | #include "bitmap.h"
|
37 | #include "http_server.h"
|
38 | }
|
39 |
|
40 | /* The examples use simple WiFi configuration that you can set via
|
41 | 'make menuconfig'.
|
42 |
|
43 | If you'd rather not, just change the below entries to strings with
|
44 | the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
|
45 |
|
46 | 0x20001 | ESP_ERR_CAMERA_NOT_DETECTED | Check SCCB connection.
|
47 | 0x20002 | ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE | Check that resolution is supported.
|
48 | 0x20003 | ESP_ERR_CAMERA_NOT_SUPPORTED | Check if camera-model is supported/activated.
|
49 | */
|
50 | #define EXAMPLE_ESP_WIFI_MODE_AP true // true:AP false:STA
|
51 | #define EXAMPLE_ESP_WIFI_SSID "ESP32-CAM"
|
52 | #define EXAMPLE_ESP_WIFI_PASS ""
|
53 |
|
54 | //#define EXAMPLE_ESP_WIFI_MODE_AP false // true:AP false:STA
|
55 | //#define EXAMPLE_ESP_WIFI_SSID "xxxxxxx"
|
56 | //#define EXAMPLE_ESP_WIFI_PASS "xxxxxxx"
|
57 |
|
58 | #define EXAMPLE_MAX_STA_CONN 1
|
59 | #define CAMERA_LED_GPIO 16
|
60 |
|
61 | #if EXAMPLE_ESP_WIFI_MODE_AP
|
62 | static void wifi_init_softap(void);
|
63 | #else
|
64 | static void wifi_init_sta(void);
|
65 | #endif
|
66 |
|
67 | static void handle_grayscale_pgm(http_context_t http_ctx, void* ctx);
|
68 | static void handle_rgb_bmp(http_context_t http_ctx, void* ctx);
|
69 | static void handle_rgb_bmp_stream(http_context_t http_ctx, void* ctx);
|
70 | static void handle_jpg(http_context_t http_ctx, void* ctx);
|
71 | static void handle_jpg_stream(http_context_t http_ctx, void* ctx);
|
72 | static esp_err_t event_handler(void *ctx, system_event_t *event);
|
73 |
|
74 |
|
75 | static const char* TAG = "camera_demo";
|
76 |
|
77 | static const char* STREAM_CONTENT_TYPE =
|
78 | "multipart/x-mixed-replace; boundary=123456789000000000000987654321";
|
79 |
|
80 | static const char* STREAM_BOUNDARY = "--123456789000000000000987654321";
|
81 |
|
82 | static EventGroupHandle_t s_wifi_event_group;
|
83 | const int CONNECTED_BIT = BIT0;
|
84 | static ip4_addr_t s_ip_addr;
|
85 | static camera_pixelformat_t s_pixel_format;
|
86 |
|
87 | #define CAMERA_PIXEL_FORMAT CAMERA_PF_JPEG
|
88 | #define CAMERA_FRAME_SIZE CAMERA_FS_SVGA
|
89 |
|
90 |
|
91 | void setup()
|
92 | {
|
93 | esp_log_level_set("wifi", ESP_LOG_INFO);
|
94 | esp_log_level_set("gpio", ESP_LOG_WARN);
|
95 | esp_log_level_set("isr", ESP_LOG_INFO);
|
96 | esp_log_level_set("http_server", ESP_LOG_NONE);
|
97 | esp_log_level_set("camera", ESP_LOG_INFO);
|
98 | esp_log_level_set("camera_xclk", ESP_LOG_NONE);
|
99 |
|
100 | esp_err_t err = nvs_flash_init();
|
101 | if (err != ESP_OK) {
|
102 | ESP_ERROR_CHECK( nvs_flash_erase() );
|
103 | ESP_ERROR_CHECK( nvs_flash_init() );
|
104 | }
|
105 |
|
106 | ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
107 | gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT);
|
108 | gpio_set_level(GPIO_NUM_16, HIGH);
|
109 |
|
110 | camera_config_t camera_config = {
|
111 | .pin_reset = 15,
|
112 | .pin_xclk = 27,
|
113 | .pin_sscb_sda = 25,
|
114 | .pin_sscb_scl = 23,
|
115 | .pin_d7 = 19,
|
116 | .pin_d6 = 36,
|
117 | .pin_d5 = 18,
|
118 | .pin_d4 = 39,
|
119 | .pin_d3 = 5,
|
120 | .pin_d2 = 34,
|
121 | .pin_d1 = 35,
|
122 | .pin_d0 = 17,
|
123 | .pin_vsync = 22,
|
124 | .pin_href = 26,
|
125 | .pin_pclk = 21,
|
126 | .xclk_freq_hz = 8000000,
|
127 | .ledc_timer = LEDC_TIMER_0,
|
128 | .ledc_channel = LEDC_CHANNEL_0,
|
129 | };
|
130 |
|
131 | camera_model_t camera_model;
|
132 | err = camera_probe(&camera_config, &camera_model);
|
133 | if (err != ESP_OK) {
|
134 | ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err);
|
135 | return;
|
136 | }
|
137 |
|
138 | if (camera_model == CAMERA_OV7725) {
|
139 | s_pixel_format = CAMERA_PIXEL_FORMAT;
|
140 | camera_config.frame_size = CAMERA_FRAME_SIZE;
|
141 | ESP_LOGI(TAG, "Detected OV7725 camera, using %s bitmap format",
|
142 | CAMERA_PIXEL_FORMAT == CAMERA_PF_GRAYSCALE ?
|
143 | "grayscale" : "RGB565");
|
144 | } else if (camera_model == CAMERA_OV2640) {
|
145 | ESP_LOGI(TAG, "Detected OV2640 camera, using JPEG format");
|
146 | s_pixel_format = CAMERA_PF_JPEG;
|
147 | camera_config.frame_size = CAMERA_FRAME_SIZE;
|
148 | camera_config.jpeg_quality = 15;
|
149 | } else {
|
150 | ESP_LOGE(TAG, "Camera not supported");
|
151 | return;
|
152 | }
|
153 |
|
154 | camera_config.pixel_format = s_pixel_format;
|
155 | err = camera_init(&camera_config);
|
156 | if (err != ESP_OK) {
|
157 | ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
|
158 | return;
|
159 | }
|
160 |
|
161 | #if EXAMPLE_ESP_WIFI_MODE_AP
|
162 | ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
|
163 | wifi_init_softap();
|
164 | #else
|
165 | ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
|
166 | wifi_init_sta();
|
167 | ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
|
168 | #endif
|
169 |
|
170 | http_server_t server;
|
171 | http_server_options_t http_options = HTTP_SERVER_OPTIONS_DEFAULT();
|
172 | ESP_ERROR_CHECK( http_server_start(&http_options, &server) );
|
173 |
|
174 | if (s_pixel_format == CAMERA_PF_GRAYSCALE) {
|
175 | ESP_ERROR_CHECK( http_register_handler(server, "/pgm", HTTP_GET, HTTP_HANDLE_RESPONSE, &handle_grayscale_pgm, NULL) );
|
176 | ESP_LOGI(TAG, "Open http://" IPSTR "/pgm for a single image/x-portable-graymap image", IP2STR(&s_ip_addr));
|
177 | }
|
178 | if (s_pixel_format == CAMERA_PF_RGB565) {
|
179 | ESP_ERROR_CHECK( http_register_handler(server, "/bmp", HTTP_GET, HTTP_HANDLE_RESPONSE, &handle_rgb_bmp, NULL) );
|
180 | ESP_LOGI(TAG, "Open http://" IPSTR "/bmp for single image/bitmap image", IP2STR(&s_ip_addr));
|
181 | ESP_ERROR_CHECK( http_register_handler(server, "/bmp_stream", HTTP_GET, HTTP_HANDLE_RESPONSE, &handle_rgb_bmp_stream, NULL) );
|
182 | ESP_LOGI(TAG, "Open http://" IPSTR "/bmp_stream for multipart/x-mixed-replace stream of bitmaps", IP2STR(&s_ip_addr));
|
183 | }
|
184 | if (s_pixel_format == CAMERA_PF_JPEG) {
|
185 | ESP_ERROR_CHECK( http_register_handler(server, "/jpg", HTTP_GET, HTTP_HANDLE_RESPONSE, &handle_jpg, NULL) );
|
186 | ESP_LOGI(TAG, "Open http://" IPSTR "/jpg for single image/jpg image", IP2STR(&s_ip_addr));
|
187 | ESP_ERROR_CHECK( http_register_handler(server, "/jpg_stream", HTTP_GET, HTTP_HANDLE_RESPONSE, &handle_jpg_stream, NULL) );
|
188 | ESP_LOGI(TAG, "Open http://" IPSTR "/jpg_stream for multipart/x-mixed-replace stream of JPEGs", IP2STR(&s_ip_addr));
|
189 | ESP_ERROR_CHECK( http_register_handler(server, "/", HTTP_GET, HTTP_HANDLE_RESPONSE, &handle_jpg_stream, NULL) );
|
190 | }
|
191 | ESP_LOGI(TAG, "Free heap: %u", xPortGetFreeHeapSize());
|
192 | ESP_LOGI(TAG, "Camera demo ready");
|
193 |
|
194 | }
|
195 |
|
196 | void loop() {}
|
197 |
|
198 | static esp_err_t write_frame(http_context_t http_ctx)
|
199 | {
|
200 | http_buffer_t fb_data = {
|
201 | .data = camera_get_fb(),
|
202 | .size = camera_get_data_size(),
|
203 | .data_is_persistent = true
|
204 | };
|
205 | return http_response_write(http_ctx, &fb_data);
|
206 | }
|
207 |
|
208 | static void handle_grayscale_pgm(http_context_t http_ctx, void* ctx)
|
209 | {
|
210 | esp_err_t err = camera_run();
|
211 | if (err != ESP_OK) {
|
212 | ESP_LOGD(TAG, "Camera capture failed with error = %d", err);
|
213 | return;
|
214 | }
|
215 | char* pgm_header_str;
|
216 | asprintf(&pgm_header_str, "P5 %d %d %d\n",
|
217 | camera_get_fb_width(), camera_get_fb_height(), 255);
|
218 | if (pgm_header_str == NULL) {
|
219 | return;
|
220 | }
|
221 |
|
222 | size_t response_size = strlen(pgm_header_str) + camera_get_data_size();
|
223 | http_response_begin(http_ctx, 200, "image/x-portable-graymap", response_size);
|
224 | http_response_set_header(http_ctx, "Content-disposition", "inline; filename=capture.pgm");
|
225 | http_buffer_t pgm_header = { .data = pgm_header_str };
|
226 | http_response_write(http_ctx, &pgm_header);
|
227 | free(pgm_header_str);
|
228 |
|
229 | write_frame(http_ctx);
|
230 | http_response_end(http_ctx);
|
231 | }
|
232 |
|
233 | static void handle_rgb_bmp(http_context_t http_ctx, void* ctx)
|
234 | {
|
235 | esp_err_t err = camera_run();
|
236 | if (err != ESP_OK) {
|
237 | ESP_LOGD(TAG, "Camera capture failed with error = %d", err);
|
238 | return;
|
239 | }
|
240 |
|
241 | bitmap_header_t* header = bmp_create_header(camera_get_fb_width(), camera_get_fb_height());
|
242 | if (header == NULL) {
|
243 | return;
|
244 | }
|
245 |
|
246 | http_response_begin(http_ctx, 200, "image/bmp", sizeof(*header) + camera_get_data_size());
|
247 | http_buffer_t bmp_header = {
|
248 | .data = header,
|
249 | .size = sizeof(*header)
|
250 | };
|
251 | http_response_set_header(http_ctx, "Content-disposition", "inline; filename=capture.bmp");
|
252 | http_response_write(http_ctx, &bmp_header);
|
253 | free(header);
|
254 |
|
255 | write_frame(http_ctx);
|
256 | http_response_end(http_ctx);
|
257 | }
|
258 |
|
259 | static void handle_jpg(http_context_t http_ctx, void* ctx)
|
260 | {
|
261 | esp_err_t err = camera_run();
|
262 | if (err != ESP_OK) {
|
263 | ESP_LOGD(TAG, "Camera capture failed with error = %d", err);
|
264 | return;
|
265 | }
|
266 |
|
267 | http_response_begin(http_ctx, 200, "image/jpeg", camera_get_data_size());
|
268 | http_response_set_header(http_ctx, "Content-disposition", "inline; filename=capture.jpg");
|
269 | write_frame(http_ctx);
|
270 | http_response_end(http_ctx);
|
271 | }
|
272 |
|
273 |
|
274 | static void handle_rgb_bmp_stream(http_context_t http_ctx, void* ctx)
|
275 | {
|
276 | http_response_begin(http_ctx, 200, STREAM_CONTENT_TYPE, HTTP_RESPONSE_SIZE_UNKNOWN);
|
277 | bitmap_header_t* header = bmp_create_header(camera_get_fb_width(), camera_get_fb_height());
|
278 | if (header == NULL) {
|
279 | return;
|
280 | }
|
281 | http_buffer_t bmp_header = {
|
282 | .data = header,
|
283 | .size = sizeof(*header)
|
284 | };
|
285 |
|
286 |
|
287 | while (true) {
|
288 | esp_err_t err = camera_run();
|
289 | if (err != ESP_OK) {
|
290 | ESP_LOGD(TAG, "Camera capture failed with error = %d", err);
|
291 | return;
|
292 | }
|
293 |
|
294 | err = http_response_begin_multipart(http_ctx, "image/bitmap",
|
295 | camera_get_data_size() + sizeof(*header));
|
296 | if (err != ESP_OK) {
|
297 | break;
|
298 | }
|
299 | err = http_response_write(http_ctx, &bmp_header);
|
300 | if (err != ESP_OK) {
|
301 | break;
|
302 | }
|
303 | err = write_frame(http_ctx);
|
304 | if (err != ESP_OK) {
|
305 | break;
|
306 | }
|
307 | err = http_response_end_multipart(http_ctx, STREAM_BOUNDARY);
|
308 | if (err != ESP_OK) {
|
309 | break;
|
310 | }
|
311 | }
|
312 |
|
313 | free(header);
|
314 | http_response_end(http_ctx);
|
315 | }
|
316 |
|
317 | static void handle_jpg_stream(http_context_t http_ctx, void* ctx)
|
318 | {
|
319 | http_response_begin(http_ctx, 200, STREAM_CONTENT_TYPE, HTTP_RESPONSE_SIZE_UNKNOWN);
|
320 |
|
321 | while (true) {
|
322 | esp_err_t err = camera_run();
|
323 | if (err != ESP_OK) {
|
324 | ESP_LOGD(TAG, "Camera capture failed with error = %d", err);
|
325 | return;
|
326 | }
|
327 | err = http_response_begin_multipart(http_ctx, "image/jpg",
|
328 | camera_get_data_size());
|
329 | if (err != ESP_OK) {
|
330 | break;
|
331 | }
|
332 | err = write_frame(http_ctx);
|
333 | if (err != ESP_OK) {
|
334 | break;
|
335 | }
|
336 | err = http_response_end_multipart(http_ctx, STREAM_BOUNDARY);
|
337 | if (err != ESP_OK) {
|
338 | break;
|
339 | }
|
340 | }
|
341 | http_response_end(http_ctx);
|
342 | }
|
343 |
|
344 |
|
345 | // /* FreeRTOS event group to signal when we are connected*/
|
346 | // static EventGroupHandle_t s_wifi_event_group;
|
347 |
|
348 | // /* The event group allows multiple bits for each event,
|
349 | // but we only care about one event - are we connected
|
350 | // to the AP with an IP? */
|
351 | // const int WIFI_CONNECTED_BIT = BIT0;
|
352 |
|
353 | static esp_err_t event_handler(void* ctx, system_event_t* event)
|
354 | {
|
355 | switch (event->event_id) {
|
356 | case SYSTEM_EVENT_STA_START:
|
357 | esp_wifi_connect();
|
358 | break;
|
359 | case SYSTEM_EVENT_STA_GOT_IP:
|
360 | ESP_LOGI(TAG, "got ip:%s", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
|
361 | s_ip_addr = event->event_info.got_ip.ip_info.ip;
|
362 | xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
|
363 | break;
|
364 | case SYSTEM_EVENT_AP_STACONNECTED:
|
365 | ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", MAC2STR(event->event_info.sta_connected.mac),
|
366 | event->event_info.sta_connected.aid);
|
367 | #if EXAMPLE_ESP_WIFI_MODE_AP
|
368 | xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
|
369 | #endif
|
370 | break;
|
371 | case SYSTEM_EVENT_AP_STADISCONNECTED:
|
372 | ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", MAC2STR(event->event_info.sta_disconnected.mac),
|
373 | event->event_info.sta_disconnected.aid);
|
374 | #if EXAMPLE_ESP_WIFI_MODE_AP
|
375 | xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
|
376 | #endif
|
377 | break;
|
378 | case SYSTEM_EVENT_STA_DISCONNECTED:
|
379 | esp_wifi_connect();
|
380 | xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
|
381 | break;
|
382 | default:
|
383 | break;
|
384 | }
|
385 | return ESP_OK;
|
386 | }
|
387 |
|
388 | #if EXAMPLE_ESP_WIFI_MODE_AP
|
389 |
|
390 | static void wifi_init_softap()
|
391 | {
|
392 | s_wifi_event_group = xEventGroupCreate();
|
393 |
|
394 | tcpip_adapter_init();
|
395 | ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
|
396 |
|
397 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
398 | ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
399 | wifi_config_t wifi_config;
|
400 | strcpy(reinterpret_cast<char*>(wifi_config.ap.ssid), EXAMPLE_ESP_WIFI_SSID);
|
401 | strcpy(reinterpret_cast<char*>(wifi_config.ap.password), EXAMPLE_ESP_WIFI_PASS);
|
402 | wifi_config.ap.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID);
|
403 | wifi_config.ap.max_connection = EXAMPLE_MAX_STA_CONN;
|
404 | wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
|
405 |
|
406 | if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
|
407 | wifi_config.ap.authmode = WIFI_AUTH_OPEN;
|
408 | }
|
409 |
|
410 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
411 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
|
412 | ESP_ERROR_CHECK(esp_wifi_start());
|
413 |
|
414 | uint8_t addr[4] = {192, 168, 4, 1};
|
415 | s_ip_addr = *(ip4_addr_t*)&addr;
|
416 |
|
417 | ESP_LOGI(TAG, "wifi_init_softap finished.SSID:%s password:%s",
|
418 | EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
|
419 | }
|
420 |
|
421 | #else
|
422 |
|
423 | static void wifi_init_sta()
|
424 | {
|
425 | s_wifi_event_group = xEventGroupCreate();
|
426 |
|
427 | tcpip_adapter_init();
|
428 | ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
|
429 |
|
430 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
431 | ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
432 | //wifi_config_t wifi_config = {
|
433 | // .sta = {.ssid = EXAMPLE_ESP_WIFI_SSID, .password = EXAMPLE_ESP_WIFI_PASS},
|
434 | //};
|
435 | wifi_config_t wifi_config { };
|
436 | strcpy((char*)wifi_config.sta.ssid, (const char*)EXAMPLE_ESP_WIFI_SSID);
|
437 | strcpy((char*)wifi_config.sta.password, (const char*)EXAMPLE_ESP_WIFI_PASS);
|
438 |
|
439 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
440 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
|
441 | ESP_ERROR_CHECK(esp_wifi_start());
|
442 |
|
443 | ESP_LOGI(TAG, "wifi_init_sta finished.");
|
444 | ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", EXAMPLE_ESP_WIFI_SSID,
|
445 | EXAMPLE_ESP_WIFI_PASS);
|
446 |
|
447 | xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY);
|
448 | }
|
449 | #endif
|