Creating a Basic Remote Video Monitor with ESP32-WROVER and ESP32-CAM
In this tutorial we will show how to create a basic remote video monitor using 2 ESP32-based boards communicating via Wi-Fi:
- The ESP32-CAM module will be used to capture pictures
- The ESP32-WROVER module will be used to display the captured images
The modules will communicate to each other using Wi-Fi (the WROVER module will act as a Wi-Fi access point and the ESP32-CAM module will connect to it). The diagram below provides an overview of the communication between the modules:Before you begin, install Visual Studio and VisualGDB 5.4 or later and ensure that you can program both modules by following our ESP32-WROVER LCD tutorial and the ESP32-CAM tutorial.
- We will begin with creating the firmware for the camera module. Start Visual Studio and open the VisualGDB ESP32 project wizard:
- On the first page of the wizard select the CMake build subsystem:
- Next, select the latest ESP32 toolchain and the latest ESP-IDF checkout:
- We will create the camera firmware by cloning the Wi-Fi station example and modifying it to serve pictures taken from the camera via HTTP. Hence, pick the wifi/getting_started/station sample:
- On the Debug Settings page select the settings necessary to debug the ESP32-CAM module. If you are not sure, follow theĀ ESP32-CAM tutorial to get JTAG to work:
- Press “Finish” to generate the project. Once the project is loaded, right-click on the components view in Solution Explorer and select Add->New Item:
- Add an empty component called “esp32-camera” to the project:
- Now we will replace our dummy component with a copy of the actual ESP32 camera library. Open the Command Prompt window and go to the project directory. Then run the following commands in the terminal:
12rmdir /s /q components\esp32-cameragit clone https://github.com/espressif/esp32-camera components/esp32-camera
If the git command doesn’t work make sure you have git installed and referenced in the PATH variable.
- Once the camera library is cloned, go back to Visual Studio and reload the project:
- Once the project is reloaded, VisualGDB will automatically display the contents of the esp32-camera library in Solution Explorer:
- Before we proceed with using the camera library, open VisualGDB Project Properties and set the Wi-Fi SSID and password to match your Wi-Fi network:
- If you are using the Custom edition of VisualGDB, we recommend enabling the raw terminal on the COM port connected to the ESP32-CAM board. For lower editions, simply use an external terminal program instead:
- Now we will add the code that will take pictures using the camera. First of all, copy the camera I/O pin definitions from the Arduino sample:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#define CAMERA_MODEL_AI_THINKER#if defined(CAMERA_MODEL_WROVER_KIT)#define PWDN_GPIO_NUM -1#define RESET_GPIO_NUM -1#define XCLK_GPIO_NUM 21#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 19#define Y4_GPIO_NUM 18#define Y3_GPIO_NUM 5#define Y2_GPIO_NUM 4#define VSYNC_GPIO_NUM 25#define HREF_GPIO_NUM 23#define PCLK_GPIO_NUM 22#elif defined(CAMERA_MODEL_M5STACK_PSRAM)#define PWDN_GPIO_NUM -1#define RESET_GPIO_NUM 15#define XCLK_GPIO_NUM 27#define SIOD_GPIO_NUM 25#define SIOC_GPIO_NUM 23#define Y9_GPIO_NUM 19#define Y8_GPIO_NUM 36#define Y7_GPIO_NUM 18#define Y6_GPIO_NUM 39#define Y5_GPIO_NUM 5#define Y4_GPIO_NUM 34#define Y3_GPIO_NUM 35#define Y2_GPIO_NUM 32#define VSYNC_GPIO_NUM 22#define HREF_GPIO_NUM 26#define PCLK_GPIO_NUM 21#elif defined(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#else#error "Camera model not selected"#endif
Then add a basic HTTP request handler that will take a picture and send it via HTTP each time it receives a request:
123456789101112131415161718192021222324252627282930313233#include <esp_camera.h>#include <esp_http_server.h>httpd_handle_t s_httpd = NULL;esp_err_t jpg_httpd_handler(httpd_req_t *req){camera_fb_t *fb = NULL;esp_err_t res = ESP_OK;size_t fb_len = 0;int64_t fr_start = esp_timer_get_time();fb = esp_camera_fb_get();if (!fb){ESP_LOGE(TAG, "Camera capture failed");httpd_resp_send_500(req);return ESP_FAIL;}res = httpd_resp_set_type(req, "image/jpeg");if (res == ESP_OK){fb_len = fb->len;res = httpd_resp_send(req, (const char *)fb->buf, fb->len);}esp_camera_fb_return(fb);int64_t fr_end = esp_timer_get_time();ESP_LOGI(TAG, "JPG: %uKB %ums", (uint32_t)(fb_len / 1024), (uint32_t)((fr_end - fr_start) / 1000));return res;}
Finally, replace the app_main() function with a version that enables the camera driver and starts an HTTP server:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061void app_main(){//Initialize NVSesp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}ESP_ERROR_CHECK(ret);ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");wifi_init_sta();camera_config_t config;config.ledc_channel = LEDC_CHANNEL_0;config.ledc_timer = LEDC_TIMER_0;config.pin_d0 = Y2_GPIO_NUM;config.pin_d1 = Y3_GPIO_NUM;config.pin_d2 = Y4_GPIO_NUM;config.pin_d3 = Y5_GPIO_NUM;config.pin_d4 = Y6_GPIO_NUM;config.pin_d5 = Y7_GPIO_NUM;config.pin_d6 = Y8_GPIO_NUM;config.pin_d7 = Y9_GPIO_NUM;config.pin_xclk = XCLK_GPIO_NUM;config.pin_pclk = PCLK_GPIO_NUM;config.pin_vsync = VSYNC_GPIO_NUM;config.pin_href = HREF_GPIO_NUM;config.pin_sscb_sda = SIOD_GPIO_NUM;config.pin_sscb_scl = SIOC_GPIO_NUM;config.pin_pwdn = PWDN_GPIO_NUM;config.pin_reset = RESET_GPIO_NUM;config.xclk_freq_hz = 20000000;config.pixel_format = PIXFORMAT_JPEG;config.frame_size = FRAMESIZE_QVGA;config.jpeg_quality = 10;config.fb_count = 1;// camera initesp_err_t err = esp_camera_init(&config);ESP_ERROR_CHECK(err);sensor_t *s = esp_camera_sensor_get();s->set_framesize(s, FRAMESIZE_QVGA);s->set_quality(s, 20);httpd_config_t httpdConfig = HTTPD_DEFAULT_CONFIG();httpd_uri_t index_uri = {.uri = "/",.method = HTTP_GET,.handler = jpg_httpd_handler,.user_ctx = NULL};if (httpd_start(&s_httpd, &httpdConfig) == ESP_OK){httpd_register_uri_handler(s_httpd, &index_uri);}} - Press F5 to build and start your program. Take a note of the IP address reported by the board via the COM port:
- Open the IP address of the board in your browser. You will see a low-resolution picture from the camera:
- Now it’s the time to create the firmware for the ESP32-WROVER board that will request the pictures from the camera and display them on the on-board LCD screen. Open another instance of Visual Studio and start the VisualGDB ESP32 project wizard:
- Proceed with the CMake build subsystem:
- This time ensure you are using ESP-IDF 3.3 or later, as the older versions do not report the IP addresses assigned to the Wi-Fi clients:
- Select the spi_master sample and press “next”:
- Select the debug settings for the ESP32-WROVER board. Do not confuse them with the settings for the ESP32-CAM module:
- Now we will modify the LCD display example to show the pictures received via Wi-Fi instead of the hardcoded JPEG image. First of all, modify the decode_image() function to accept an arbitrary JPEG buffer instead of the one in the FLASH memory:
See the ESP32-WROVER LCD tutorial for more details about the roles of various LCD-related functions.
- Now we will proceed creating a Wi-Fi access point. Add the following code to the main source file:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "freertos/event_groups.h"#include "esp_system.h"#include "esp_wifi.h"#include "esp_event_loop.h"#include "esp_log.h"#include "nvs_flash.h"#include "esp_http_client.h"ip4_addr_t s_ClientIP;static EventGroupHandle_t s_wifi_event_group;const int WIFI_CONNECTED_BIT = BIT0;static esp_err_t event_handler(void *ctx, system_event_t *event){switch (event->event_id){case SYSTEM_EVENT_AP_STAIPASSIGNED:s_ClientIP = event->event_info.ap_staipassigned.ip;xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);break;default:break;}return ESP_OK;}#define WIFI_SSID "espnet"#define WIFI_PASSWORD "sysprogs"void wifi_init_softap(){s_wifi_event_group = xEventGroupCreate();esp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}ESP_ERROR_CHECK(ret);tcpip_adapter_init();ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));wifi_config_t wifi_config = {.ap = {.ssid = WIFI_SSID,.ssid_len = strlen(WIFI_SSID),.password = WIFI_PASSWORD,.max_connection = 4,.authmode = WIFI_AUTH_WPA2_PSK},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());}
Ensure that the Wi-Fi SSID and password on the WROVER board match the SSID and password in the ESP32-CAM firmware and do not conflict with your normal Wi-Fi network.Note how the handler for the SYSTEM_EVENT_AP_STAIPASSIGNED event (raised by ESP-IDF when a client connected to our access point gets assigned an IP address) saves the address assigned to the last client into the s_ClientIP variable.
- Add the following function that will continuously download the JPEG images from the last connected client and will display them on the LCD screen:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859#include "decode_image.h"void GetImagesFromClient(spi_device_handle_t spi){const int kMaxJpegFileSize = 32768;char *pBuf = (char *)malloc(kMaxJpegFileSize);uint16_t *pDMABuf = heap_caps_malloc(320 * PARALLEL_LINES * sizeof(uint16_t), MALLOC_CAP_DMA);for (;;){char url[128];sprintf(url, "http://%d.%d.%d.%d/", (s_ClientIP.addr >> 0) & 0xFF,(s_ClientIP.addr >> 8) & 0xFF,(s_ClientIP.addr >> 16) & 0xFF,(s_ClientIP.addr >> 24) & 0xFF);esp_http_client_config_t config = {.url = url,};esp_http_client_handle_t client = esp_http_client_init(&config);esp_http_client_open(client, 0);int content_length = esp_http_client_fetch_headers(client);int total_read_len = 0, read_len = 0;if (total_read_len < content_length && content_length <= kMaxJpegFileSize)read_len = esp_http_client_read(client, pBuf, content_length);esp_http_client_close(client);esp_http_client_cleanup(client);if (read_len){uint16_t **decoded = NULL;esp_err_t rc = decode_image(pBuf, read_len, &decoded);if (!rc){for (int y = 0; y < 240; y += PARALLEL_LINES){for (int yo = 0; yo < PARALLEL_LINES; yo++)memcpy(pDMABuf + yo * 320, decoded[y + yo], 320 * sizeof(uint16_t));send_lines(spi, y, pDMABuf);send_line_finish(spi);}}if (decoded != NULL){for (int i = 0; i < 256; i++){free((decoded)[i]);}free(decoded);}}}}
- Finally, remove the call to the pretty_effect_init() function (and the function itself) and replace the end of app_init() with the following code:
123wifi_init_softap();xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);GetImagesFromClient(spi);
- Now you can build and run the WROVER firmware. Once it starts up, power up the ESP32-CAM board and check the WROVER output for messages regarding the Wi-Fi clients:
If the boards do not connect, ensure they both use the same Wi-Fi SSID and password and check that it doesn’t conflict with your main Wi-Fi connection.
- Once the boards connect, you the pictures taken by the camera will be shown on the LCD display of the WROVER module:
You can find the source code of the projects shown in this tutorial on our GitHub repository. If you would like both boards to connect to your regular Wi-Fi network instead, replace the call to the wifi_init_softap() function in the WROVER firmware by an equivalent of the wifi_init_sta() function from the ESP32-CAM firmware and update the WROVER firmware to fetch the images from a fixed IP address.