From abf4e2e0b5d0d946352a92169101aacbd5378609 Mon Sep 17 00:00:00 2001 From: claus Date: Wed, 6 Sep 2023 23:11:15 -0300 Subject: [PATCH] Working demo. --- .gitignore | 1 + sdkconfig.esp32cam | 10 +-- src/main.c | 161 ++++++++++++++++++++++++++++++++++++++------- src/wifi.c | 118 +++++++++++++++++++++++++++++++++ src/wifi.h | 26 ++++++++ 5 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 src/wifi.c create mode 100644 src/wifi.h diff --git a/.gitignore b/.gitignore index 89cc49c..46fe6f7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +secrets.h \ No newline at end of file diff --git a/sdkconfig.esp32cam b/sdkconfig.esp32cam index a62cdff..9e45a1d 100644 --- a/sdkconfig.esp32cam +++ b/sdkconfig.esp32cam @@ -1149,12 +1149,14 @@ CONFIG_IEEE802154_PENDING_TABLE_SIZE=20 # CONFIG_LOG_DEFAULT_LEVEL_NONE is not set # CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set # CONFIG_LOG_DEFAULT_LEVEL_WARN is not set -# CONFIG_LOG_DEFAULT_LEVEL_INFO is not set +CONFIG_LOG_DEFAULT_LEVEL_INFO=y # CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set -CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y -CONFIG_LOG_DEFAULT_LEVEL=5 +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y -CONFIG_LOG_MAXIMUM_LEVEL=5 +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=3 # CONFIG_LOG_COLORS is not set CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set diff --git a/src/main.c b/src/main.c index 846e9b6..ea4a2a2 100644 --- a/src/main.c +++ b/src/main.c @@ -29,14 +29,30 @@ // ================================ CODE ====================================== -#include #include #include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" + +#include "esp_camera.h" +#include "esp_http_server.h" +#include "esp_timer.h" + +#include "wifi.h" + +static const char *TAG = "esp32-cam Webserver"; + +#define PART_BOUNDARY "123456789000000000000987654321" +static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + + +#include #include #include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" // support IDF 5.x #ifndef portTICK_RATE_MS @@ -93,7 +109,6 @@ #endif -static const char *TAG = "example:take_picture"; #if ESP_CAMERA_SUPPORTED static camera_config_t camera_config = { @@ -120,12 +135,16 @@ static camera_config_t camera_config = { .ledc_timer = LEDC_TIMER_0, .ledc_channel = LEDC_CHANNEL_0, - .pixel_format = PIXFORMAT_RGB565, //YUV422,GRAYSCALE,RGB565,JPEG - .frame_size = FRAMESIZE_QVGA, //QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates. + // .pixel_format = PIXFORMAT_RGB565, //YUV422,GRAYSCALE,RGB565,JPEG + .pixel_format = PIXFORMAT_JPEG, //YUV422,GRAYSCALE,RGB565,JPEG + // .frame_size = FRAMESIZE_QVGA, //QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates. + .frame_size = FRAMESIZE_240X240, //QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates. + // .jpeg_quality = 12, //0-63, for OV series camera sensors, lower number means higher quality .jpeg_quality = 12, //0-63, for OV series camera sensors, lower number means higher quality - .fb_count = 1, //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode. - .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .fb_count = 2, //When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode. + // .grab_mode = CAMERA_GRAB_WHEN_EMPTY, + .grab_mode = CAMERA_GRAB_LATEST, }; static esp_err_t init_camera(void) @@ -142,26 +161,122 @@ static esp_err_t init_camera(void) } #endif -void app_main(void) + + +esp_err_t jpg_stream_httpd_handler(httpd_req_t *req){ + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len; + uint8_t * _jpg_buf; + char * part_buf[64]; + static int64_t last_frame = 0; + if(!last_frame) { + last_frame = esp_timer_get_time(); + } + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if(res != ESP_OK){ + return res; + } + + while(true){ + fb = esp_camera_fb_get(); + if (!fb) { + ESP_LOGE(TAG, "Camera capture failed"); + res = ESP_FAIL; + break; + } + if(fb->format != PIXFORMAT_JPEG){ + bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + if(!jpeg_converted){ + ESP_LOGE(TAG, "JPEG compression failed"); + esp_camera_fb_return(fb); + res = ESP_FAIL; + } + } else { + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; + } + + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); + } + if(res == ESP_OK){ + size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); + + res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); + } + if(res == ESP_OK){ + res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); + } + if(fb->format != PIXFORMAT_JPEG){ + free(_jpg_buf); + } + esp_camera_fb_return(fb); + if(res != ESP_OK){ + break; + } + int64_t fr_end = esp_timer_get_time(); + int64_t frame_time = fr_end - last_frame; + last_frame = fr_end; + frame_time /= 1000; + ESP_LOGI(TAG, "MJPG: %uKB %ums (%.1ffps)", + (unsigned int)(uint32_t)(_jpg_buf_len/1024), + (unsigned int)(uint32_t)frame_time, 1000.0 / (unsigned int)(uint32_t)frame_time); + } + + last_frame = 0; + return res; +} + + +httpd_uri_t uri_get = { + .uri = "/", + .method = HTTP_GET, + .handler = jpg_stream_httpd_handler, + .user_ctx = NULL}; + + +httpd_handle_t setup_server(void) { -#if ESP_CAMERA_SUPPORTED - if(ESP_OK != init_camera()) { - return; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + httpd_handle_t stream_httpd = NULL; + + if (httpd_start(&stream_httpd , &config) == ESP_OK) + { + httpd_register_uri_handler(stream_httpd , &uri_get); } - while (1) + return stream_httpd; +} + + + +void app_main() +{ + esp_err_t err; + + // Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_LOGI(TAG, "Taking picture..."); - camera_fb_t *pic = esp_camera_fb_get(); + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } - // use pic->buf to access the image - ESP_LOGI(TAG, "Picture taken! Its size was: %zu bytes", pic->len); - esp_camera_fb_return(pic); + connect_wifi(); - vTaskDelay(5000 / portTICK_RATE_MS); + if (wifi_connect_status) + { + err = init_camera(); + if (err != ESP_OK) + { + printf("err: %s\n", esp_err_to_name(err)); + return; + } + setup_server(); + ESP_LOGI(TAG, "ESP32 CAM Web Server is up and running\n"); } -#else - ESP_LOGE(TAG, "Camera support is not available for this chip"); - return; -#endif + else + ESP_LOGI(TAG, "Failed to connected with Wi-Fi, check your network Credentials\n"); } \ No newline at end of file diff --git a/src/wifi.c b/src/wifi.c new file mode 100644 index 0000000..9d0c7cf --- /dev/null +++ b/src/wifi.c @@ -0,0 +1,118 @@ +#include "wifi.h" +#include "secrets.h" + +int wifi_connect_status = 0; +static const char *TAG = "Connect_WiFi"; +int s_retry_num = 0; + + + +#define MAXIMUM_RETRY 5 +/* FreeRTOS event group to signal when we are connected*/ +EventGroupHandle_t s_wifi_event_group; + +/* The event group allows multiple bits for each event, but we only care about two events: + * - we are connected to the AP with an IP + * - we failed to connect after the maximum amount of retries */ +#define WIFI_CONNECTED_BIT BIT0 +#define WIFI_FAIL_BIT BIT1 + +static void event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) + { + esp_wifi_connect(); + } + else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) + { + if (s_retry_num < MAXIMUM_RETRY) + { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "retry to connect to the AP"); + } + else + { + xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT); + } + wifi_connect_status = 0; + ESP_LOGI(TAG, "connect to the AP fail"); + } + else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) + { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + ESP_LOGW(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip)); + s_retry_num = 0; + xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); + wifi_connect_status = 1; + } +} + +void connect_wifi(void) +{ + s_wifi_event_group = xEventGroupCreate(); + + ESP_ERROR_CHECK(esp_netif_init()); + + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_create_default_wifi_sta(); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_event_handler_instance_t instance_any_id; + esp_event_handler_instance_t instance_got_ip; + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + ESP_EVENT_ANY_ID, + &event_handler, + NULL, + &instance_any_id)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, + IP_EVENT_STA_GOT_IP, + &event_handler, + NULL, + &instance_got_ip)); + + wifi_config_t wifi_config = { + .sta = { + .ssid = WIFI_SSID, + .password = WIFI_PASSWORD, + /* Setting a password implies station will connect to all security modes including WEP/WPA. + * However these modes are deprecated and not advisable to be used. Incase your Access point + * doesn't support WPA2, these mode can be enabled by commenting below line */ + .threshold.authmode = WIFI_AUTH_WPA2_PSK, + }, + }; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + + /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum + * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */ + EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, + WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, + pdFALSE, + pdFALSE, + portMAX_DELAY); + + /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually + * happened. */ + if (bits & WIFI_CONNECTED_BIT) + { + ESP_LOGI(TAG, "connected to ap SSID:%s password:%s", + WIFI_SSID, WIFI_PASSWORD); + } + else if (bits & WIFI_FAIL_BIT) + { + ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s", + WIFI_SSID, WIFI_PASSWORD); + } + else + { + ESP_LOGE(TAG, "UNEXPECTED EVENT"); + } + vEventGroupDelete(s_wifi_event_group); +} \ No newline at end of file diff --git a/src/wifi.h b/src/wifi.h new file mode 100644 index 0000000..f0b2b77 --- /dev/null +++ b/src/wifi.h @@ -0,0 +1,26 @@ +#ifndef WIFI_H +#define WIFI_H + + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_netif.h" +#include "driver/gpio.h" +#include +#include +#include +#include + +extern int wifi_connect_status; + +void connect_wifi(void); + + +#endif /* WIFI_H */