{"id":4771,"date":"2020-02-26T10:31:06","date_gmt":"2020-02-26T18:31:06","guid":{"rendered":"https:\/\/visualgdb.com\/w\/?p=4771"},"modified":"2025-09-09T19:28:19","modified_gmt":"2025-09-10T02:28:19","slug":"creating-a-basic-remote-video-monitor-with-esp32-wrover-and-esp32-cam","status":"publish","type":"post","link":"https:\/\/visualgdb.com\/tutorials\/esp32\/remotevideo\/","title":{"rendered":"Creating a Basic Remote Video Monitor with ESP32-WROVER and ESP32-CAM"},"content":{"rendered":"<p>In this tutorial we will show how to create a basic remote video monitor using 2 ESP32-based boards communicating via Wi-Fi:<\/p>\n<ul>\n<li>The <a href=\"https:\/\/www.seeedstudio.com\/ESP32-CAM-Development-Board-with-camer-p-3153.html\">ESP32-CAM<\/a> module will be used to capture pictures<\/li>\n<li>The <a href=\"https:\/\/docs.espressif.com\/projects\/esp-idf\/en\/latest\/get-started\/get-started-wrover-kit.html\">ESP32-WROVER module<\/a> will be used to display the captured images<\/li>\n<\/ul>\n<p>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:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/layout.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4772\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/layout.png\" alt=\"\" width=\"1526\" height=\"470\" \/><\/a>Before you begin, install Visual Studio and VisualGDB 5.4 or later and ensure that you can program both modules by following our <a href=\"https:\/\/visualgdb.com\/tutorials\/esp32\/lcd\">ESP32-WROVER LCD tutorial<\/a> and the <a href=\"https:\/\/visualgdb.com\/esp32\/camera\">ESP32-CAM tutorial<\/a>.<\/p>\n<ol>\n<li>We will begin with creating the firmware for the camera module. Start Visual Studio and open the VisualGDB ESP32 project wizard:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/01-prjname.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4773\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/01-prjname.png\" alt=\"\" width=\"941\" height=\"653\" \/><\/a><\/li>\n<li>On the first page of the wizard select the CMake build subsystem:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/02-cmake-2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4774\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/02-cmake-2.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>Next, select the latest ESP32 toolchain and the latest ESP-IDF checkout:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/03-idf.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4775\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/03-idf.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><strong>Update:<\/strong> For better compatibility with the latest ESP32 tools, we recommend selecting the <a href=\"https:\/\/visualgdb.com\/documentation\/espidf\/consolidated\/\">consolidated toolchain<\/a> instead.<\/li>\n<li>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 <strong>wifi\/getting_started\/station<\/strong> sample:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/04-station.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4776\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/04-station.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>On the Debug Settings page select the settings necessary to debug the ESP32-CAM module. If you are not sure, follow the\u00a0<a href=\"https:\/\/visualgdb.com\/esp32\/camera\">ESP32-CAM tutorial<\/a> to get JTAG to work: <a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/05-debug-3.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4777\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/05-debug-3.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>Press &#8220;Finish&#8221; to generate the project. Once the project is loaded, right-click on the components view in Solution Explorer and select Add-&gt;New Item:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/06-newcomponent.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4778\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/06-newcomponent.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>Add an empty component called &#8220;esp32-camera&#8221; to the project:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/07-camera.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4779\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/07-camera.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>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:\n<pre class=\"\">rmdir \/s \/q components\\esp32-camera\r\ngit clone https:\/\/github.com\/espressif\/esp32-camera components\/esp32-camera<\/pre>\n<p><a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/02\/cam.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-6450\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2020\/02\/cam.png\" alt=\"\" width=\"979\" height=\"512\" \/><\/a>If the <strong>git<\/strong> command doesn&#8217;t work make sure you have git installed and referenced in the PATH variable.<\/li>\n<li>Once the camera library is cloned, go back to Visual Studio and reload the project:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/10-reload.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4782\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/10-reload.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>Once the project is reloaded, VisualGDB will automatically display the contents of the esp32-camera library in Solution Explorer:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/11-loaded.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4783\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/11-loaded.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>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:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/12-ssid.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4784\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/12-ssid.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>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:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/13-terminal.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4785\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/13-terminal.png\" alt=\"\" width=\"838\" height=\"565\" \/><\/a><\/li>\n<li>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:\n<pre class=\"\">#define CAMERA_MODEL_AI_THINKER\r\n\r\n#if defined(CAMERA_MODEL_WROVER_KIT)\r\n#define PWDN_GPIO_NUM -1\r\n#define RESET_GPIO_NUM -1\r\n#define XCLK_GPIO_NUM 21\r\n#define SIOD_GPIO_NUM 26\r\n#define SIOC_GPIO_NUM 27\r\n\r\n#define Y9_GPIO_NUM 35\r\n#define Y8_GPIO_NUM 34\r\n#define Y7_GPIO_NUM 39\r\n#define Y6_GPIO_NUM 36\r\n#define Y5_GPIO_NUM 19\r\n#define Y4_GPIO_NUM 18\r\n#define Y3_GPIO_NUM 5\r\n#define Y2_GPIO_NUM 4\r\n#define VSYNC_GPIO_NUM 25\r\n#define HREF_GPIO_NUM 23\r\n#define PCLK_GPIO_NUM 22\r\n\r\n#elif defined(CAMERA_MODEL_M5STACK_PSRAM)\r\n#define PWDN_GPIO_NUM -1\r\n#define RESET_GPIO_NUM 15\r\n#define XCLK_GPIO_NUM 27\r\n#define SIOD_GPIO_NUM 25\r\n#define SIOC_GPIO_NUM 23\r\n\r\n#define Y9_GPIO_NUM 19\r\n#define Y8_GPIO_NUM 36\r\n#define Y7_GPIO_NUM 18\r\n#define Y6_GPIO_NUM 39\r\n#define Y5_GPIO_NUM 5\r\n#define Y4_GPIO_NUM 34\r\n#define Y3_GPIO_NUM 35\r\n#define Y2_GPIO_NUM 32\r\n#define VSYNC_GPIO_NUM 22\r\n#define HREF_GPIO_NUM 26\r\n#define PCLK_GPIO_NUM 21\r\n\r\n#elif defined(CAMERA_MODEL_AI_THINKER)\r\n#define PWDN_GPIO_NUM 32\r\n#define RESET_GPIO_NUM -1\r\n#define XCLK_GPIO_NUM 0\r\n#define SIOD_GPIO_NUM 26\r\n#define SIOC_GPIO_NUM 27\r\n\r\n#define Y9_GPIO_NUM 35\r\n#define Y8_GPIO_NUM 34\r\n#define Y7_GPIO_NUM 39\r\n#define Y6_GPIO_NUM 36\r\n#define Y5_GPIO_NUM 21\r\n#define Y4_GPIO_NUM 19\r\n#define Y3_GPIO_NUM 18\r\n#define Y2_GPIO_NUM 5\r\n#define VSYNC_GPIO_NUM 25\r\n#define HREF_GPIO_NUM 23\r\n#define PCLK_GPIO_NUM 22\r\n\r\n#else\r\n#error \"Camera model not selected\"\r\n#endif<\/pre>\n<p>Then add a basic HTTP request handler that will take a picture and send it via HTTP each time it receives a request:<\/p>\n<pre class=\"\">#include &lt;esp_camera.h&gt;\r\n#include &lt;esp_http_server.h&gt;\r\n\r\nhttpd_handle_t s_httpd = NULL;\r\n\r\nesp_err_t jpg_httpd_handler(httpd_req_t *req)\r\n{\r\n    camera_fb_t *fb = NULL;\r\n    esp_err_t res = ESP_OK;\r\n    size_t fb_len = 0;\r\n    int64_t fr_start = esp_timer_get_time();\r\n\r\n    fb = esp_camera_fb_get();\r\n    if (!fb)\r\n    {\r\n        ESP_LOGE(TAG, \"Camera capture failed\");\r\n        httpd_resp_send_500(req);\r\n        return ESP_FAIL;\r\n    }\r\n    \r\n    res = httpd_resp_set_type(req, \"image\/jpeg\");\r\n\r\n    if (res == ESP_OK)\r\n    {\r\n        fb_len = fb-&gt;len;\r\n        res = httpd_resp_send(req, (const char *)fb-&gt;buf, fb-&gt;len);\r\n    }\r\n    \r\n    esp_camera_fb_return(fb);\r\n    int64_t fr_end = esp_timer_get_time();\r\n    ESP_LOGI(TAG, \"JPG: %uKB %ums\", (uint32_t)(fb_len \/ 1024), (uint32_t)((fr_end - fr_start) \/ 1000));\r\n    return res;\r\n}<\/pre>\n<p>Finally, replace the app_main() function with a version that enables the camera driver and starts an HTTP server:<\/p>\n<pre class=\"\">void app_main()\r\n{\r\n    \/\/Initialize NVS\r\n    esp_err_t ret = nvs_flash_init();\r\n    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)\r\n    {\r\n        ESP_ERROR_CHECK(nvs_flash_erase());\r\n        ret = nvs_flash_init();\r\n    }\r\n    ESP_ERROR_CHECK(ret);\r\n\r\n    ESP_LOGI(TAG, \"ESP_WIFI_MODE_STA\");\r\n    wifi_init_sta();\r\n\r\n    camera_config_t config;\r\n    config.ledc_channel = LEDC_CHANNEL_0;\r\n    config.ledc_timer = LEDC_TIMER_0;\r\n    config.pin_d0 = Y2_GPIO_NUM;\r\n    config.pin_d1 = Y3_GPIO_NUM;\r\n    config.pin_d2 = Y4_GPIO_NUM;\r\n    config.pin_d3 = Y5_GPIO_NUM;\r\n    config.pin_d4 = Y6_GPIO_NUM;\r\n    config.pin_d5 = Y7_GPIO_NUM;\r\n    config.pin_d6 = Y8_GPIO_NUM;\r\n    config.pin_d7 = Y9_GPIO_NUM;\r\n    config.pin_xclk = XCLK_GPIO_NUM;\r\n    config.pin_pclk = PCLK_GPIO_NUM;\r\n    config.pin_vsync = VSYNC_GPIO_NUM;\r\n    config.pin_href = HREF_GPIO_NUM;\r\n    config.pin_sscb_sda = SIOD_GPIO_NUM;\r\n    config.pin_sscb_scl = SIOC_GPIO_NUM;\r\n    config.pin_pwdn = PWDN_GPIO_NUM;\r\n    config.pin_reset = RESET_GPIO_NUM;\r\n    config.xclk_freq_hz = 20000000;\r\n    config.pixel_format = PIXFORMAT_JPEG;\r\n\r\n    config.frame_size = FRAMESIZE_QVGA;\r\n    config.jpeg_quality = 10;\r\n    config.fb_count = 1;\r\n\r\n    \/\/ camera init\r\n    esp_err_t err = esp_camera_init(&amp;config);\r\n    ESP_ERROR_CHECK(err);\r\n\r\n    sensor_t *s = esp_camera_sensor_get();\r\n    s-&gt;set_framesize(s, FRAMESIZE_QVGA);\r\n    s-&gt;set_quality(s, 20);\r\n\r\n    httpd_config_t httpdConfig = HTTPD_DEFAULT_CONFIG();\r\n\r\n    httpd_uri_t index_uri = {\r\n        .uri = \"\/\",\r\n        .method = HTTP_GET,\r\n        .handler = jpg_httpd_handler,\r\n        .user_ctx = NULL};\r\n\r\n    if (httpd_start(&amp;s_httpd, &amp;httpdConfig) == ESP_OK)\r\n    {\r\n        httpd_register_uri_handler(s_httpd, &amp;index_uri);\r\n    }\r\n}\r\n<\/pre>\n<\/li>\n<li>Press F5 to build and start your program. Take a note of the IP address reported by the board via the COM port:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/14-ipaddr.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4786\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/14-ipaddr.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>Open the IP address of the board in your browser. You will see a low-resolution picture from the camera:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/15-screenshot.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4795\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/15-screenshot.jpg\" alt=\"\" width=\"447\" height=\"470\" \/><\/a><\/li>\n<li>Now it&#8217;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:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/17-lcdproj.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4787\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/17-lcdproj.png\" alt=\"\" width=\"941\" height=\"647\" \/><\/a><\/li>\n<li>Proceed with the CMake build subsystem:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/18-cmake.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4788\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/18-cmake.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>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:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/19-toolchain.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4789\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/19-toolchain.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>Select the <strong>spi_master<\/strong> sample and press &#8220;next&#8221;:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/20-spi.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4790\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/20-spi.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>Select the debug settings for the ESP32-WROVER board. Do not confuse them with the settings for the ESP32-CAM module:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/21-debug.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4791\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/21-debug.png\" alt=\"\" width=\"856\" height=\"693\" \/><\/a><\/li>\n<li>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 <strong>decode_image()<\/strong> function to accept an arbitrary JPEG buffer instead of the one in the FLASH memory:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/25-buffer.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4792\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/25-buffer.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a>See the <a href=\"https:\/\/visualgdb.com\/tutorials\/esp32\/lcd\">ESP32-WROVER LCD tutorial<\/a> for more details about the roles of various LCD-related functions.<\/li>\n<li>Now we will proceed creating a Wi-Fi access point. Add the following code to the main source file:\n<pre class=\"\">#include \"freertos\/FreeRTOS.h\"\r\n#include \"freertos\/task.h\"\r\n#include \"freertos\/event_groups.h\"\r\n#include \"esp_system.h\"\r\n#include \"esp_wifi.h\"\r\n#include \"esp_event_loop.h\"\r\n#include \"esp_log.h\"\r\n#include \"nvs_flash.h\"\r\n#include \"esp_http_client.h\"\r\n\r\nip4_addr_t s_ClientIP;\r\nstatic EventGroupHandle_t s_wifi_event_group;\r\nconst int WIFI_CONNECTED_BIT = BIT0;\r\n\r\nstatic esp_err_t event_handler(void *ctx, system_event_t *event)\r\n{\r\n    switch (event-&gt;event_id)\r\n    {\r\n    case SYSTEM_EVENT_AP_STAIPASSIGNED:\r\n        s_ClientIP = event-&gt;event_info.ap_staipassigned.ip;\r\n        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);\r\n        break;\r\n    default:\r\n        break;\r\n    }\r\n    return ESP_OK;\r\n}\r\n\r\n#define WIFI_SSID \"espnet\"\r\n#define WIFI_PASSWORD \"sysprogs\"\r\n\r\nvoid wifi_init_softap()\r\n{\r\n    s_wifi_event_group = xEventGroupCreate();\r\n    \r\n    esp_err_t ret = nvs_flash_init();\r\n    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)\r\n    {\r\n        ESP_ERROR_CHECK(nvs_flash_erase());\r\n        ret = nvs_flash_init();\r\n    }\r\n    ESP_ERROR_CHECK(ret);\r\n\r\n    tcpip_adapter_init();\r\n    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));\r\n\r\n    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();\r\n    ESP_ERROR_CHECK(esp_wifi_init(&amp;cfg));\r\n    wifi_config_t wifi_config = {\r\n        .ap = {\r\n            .ssid = WIFI_SSID,\r\n            .ssid_len = strlen(WIFI_SSID),\r\n            .password = WIFI_PASSWORD,\r\n            .max_connection = 4,\r\n            .authmode = WIFI_AUTH_WPA2_PSK},\r\n    };\r\n\r\n    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));\r\n    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &amp;wifi_config));\r\n    ESP_ERROR_CHECK(esp_wifi_start());\r\n}<\/pre>\n<p>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.<\/p>\n<p>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 <strong>s_ClientIP<\/strong> variable.<\/li>\n<li>Add the following function that will continuously download the JPEG images from the last connected client and will display them on the LCD screen:\n<pre class=\"\">#include \"decode_image.h\"\r\n\r\nvoid GetImagesFromClient(spi_device_handle_t spi)\r\n{\r\n    const int kMaxJpegFileSize = 32768;\r\n    char *pBuf = (char *)malloc(kMaxJpegFileSize);\r\n\r\n    uint16_t *pDMABuf = heap_caps_malloc(320 * PARALLEL_LINES * sizeof(uint16_t), MALLOC_CAP_DMA);\r\n\r\n    for (;;)\r\n    {\r\n        char url[128];\r\n        sprintf(url, \"http:\/\/%d.%d.%d.%d\/\", (s_ClientIP.addr &gt;&gt; 0) &amp; 0xFF,\r\n                (s_ClientIP.addr &gt;&gt; 8) &amp; 0xFF,\r\n                (s_ClientIP.addr &gt;&gt; 16) &amp; 0xFF,\r\n                (s_ClientIP.addr &gt;&gt; 24) &amp; 0xFF);\r\n\r\n        esp_http_client_config_t config = {\r\n            .url = url,\r\n        };\r\n\r\n        esp_http_client_handle_t client = esp_http_client_init(&amp;config);\r\n\r\n        esp_http_client_open(client, 0);\r\n        int content_length = esp_http_client_fetch_headers(client);\r\n        int total_read_len = 0, read_len = 0;\r\n        if (total_read_len &lt; content_length &amp;&amp; content_length &lt;= kMaxJpegFileSize)\r\n            read_len = esp_http_client_read(client, pBuf, content_length);\r\n\r\n        esp_http_client_close(client);\r\n        esp_http_client_cleanup(client);\r\n\r\n        if (read_len)\r\n        {\r\n            uint16_t **decoded = NULL;\r\n            esp_err_t rc = decode_image(pBuf, read_len, &amp;decoded);\r\n            if (!rc)\r\n            {\r\n                for (int y = 0; y &lt; 240; y += PARALLEL_LINES)\r\n                {\r\n                    for (int yo = 0; yo &lt; PARALLEL_LINES; yo++)\r\n                        memcpy(pDMABuf + yo * 320, decoded[y + yo], 320 * sizeof(uint16_t));\r\n\r\n                    send_lines(spi, y, pDMABuf);\r\n                    send_line_finish(spi);\r\n                }\r\n            }\r\n\r\n            if (decoded != NULL)\r\n            {\r\n                for (int i = 0; i &lt; 256; i++)\r\n                {\r\n                    free((decoded)[i]);\r\n                }\r\n                free(decoded);\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<\/li>\n<li>Finally, remove the call to the <strong>pretty_effect_init()<\/strong> function (and the function itself) and replace the end of <strong>app_init()<\/strong> with the following code:\n<pre class=\"\">    wifi_init_softap();\r\n    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);\r\n    GetImagesFromClient(spi);<\/pre>\n<p><a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/26-main.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4793\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/26-main.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a><\/li>\n<li>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:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/27-client.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4794\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/27-client.png\" alt=\"\" width=\"1218\" height=\"818\" \/><\/a>If the boards do not connect, ensure they both use the same Wi-Fi SSID and password and check that it doesn&#8217;t conflict with your main Wi-Fi connection.<\/li>\n<li>Once the boards connect, you the pictures taken by the camera will be shown on the LCD display of the WROVER module:<a href=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/28-display.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4797\" src=\"https:\/\/visualgdb.com\/w\/wp-content\/uploads\/2019\/06\/28-display.jpg\" alt=\"\" width=\"1280\" height=\"1125\" \/><\/a><\/li>\n<\/ol>\n<p>You can find the source code of the projects shown in this tutorial on our <a href=\"https:\/\/github.com\/sysprogs\/tutorials\/tree\/master\/visualgdb\/esp32\/CameraToLCDDemo\">GitHub repository<\/a>. If you would like both boards to connect to your regular Wi-Fi network instead, replace the call to the <strong>wifi_init_softap()<\/strong> function in the WROVER firmware by an equivalent of the <strong>wifi_init_sta()<\/strong> function from the ESP32-CAM firmware and update the WROVER firmware to fetch the images from a fixed IP address.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial we will show how to create a basic remote video monitor using 2 ESP32-based boards communicating via<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[142],"tags":[103,138,56,99],"_links":{"self":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/4771"}],"collection":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/comments?post=4771"}],"version-history":[{"count":5,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/4771\/revisions"}],"predecessor-version":[{"id":9037,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/posts\/4771\/revisions\/9037"}],"wp:attachment":[{"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/media?parent=4771"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/categories?post=4771"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/visualgdb.com\/w\/wp-json\/wp\/v2\/tags?post=4771"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}