From 66b00a14497fe4730991b242ad00bebbcfe0301c Mon Sep 17 00:00:00 2001 From: Ye Hang Yang Date: Wed, 8 Sep 2021 20:04:59 +0800 Subject: [PATCH] :recycle: common -> modules --- components/common/CMakeLists.txt | 3 - components/common/include/app_camera.hpp | 53 - components/common/include/app_lcd.h | 18 - components/modules/CMakeLists.txt | 31 + components/{common => modules}/Kconfig | 166 +- .../{common => modules/ai}/app_common.cpp | 36 +- .../include => modules/ai}/app_common.hpp | 29 +- .../ai/who_cat_face_detection.cpp} | 0 .../modules/ai/who_cat_face_detection.hpp | 9 + .../modules/ai/who_motion_detection.hpp | 9 + .../camera/who_camera.c} | 54 +- .../camera/who_camera.h} | 114 +- .../lcd}/wallpaper_128x240_rgb565.h | 0 .../app_lcd.c => modules/lcd/who_lcd.c} | 69 +- components/modules/lcd/who_lcd.h | 27 + .../app_led.c => modules/led/who_led.c} | 2 +- .../app_led.h => modules/led/who_led.h} | 0 components/modules/web/app_httpd.cpp | 778 +++++++++ components/modules/web/app_httpd.hpp | 8 + components/modules/web/app_mdns.c | 250 +++ components/modules/web/app_mdns.h | 16 + components/modules/web/app_wifi.c | 196 +++ components/modules/web/app_wifi.h | 37 + components/modules/web/www/compress_pages.sh | 7 + components/modules/web/www/index_ov2640.html | 1152 ++++++++++++++ .../modules/web/www/index_ov2640.html.gz | Bin 0 -> 6787 bytes components/modules/web/www/index_ov3660.html | 1368 ++++++++++++++++ .../modules/web/www/index_ov3660.html.gz | Bin 0 -> 8887 bytes components/modules/web/www/index_ov5640.html | 1391 +++++++++++++++++ .../modules/web/www/index_ov5640.html.gz | Bin 0 -> 9124 bytes components/modules/web/www/monitor.html | 1012 ++++++++++++ components/modules/web/www/monitor.html.gz | Bin 0 -> 37438 bytes 32 files changed, 6562 insertions(+), 273 deletions(-) delete mode 100644 components/common/CMakeLists.txt delete mode 100644 components/common/include/app_camera.hpp delete mode 100644 components/common/include/app_lcd.h create mode 100644 components/modules/CMakeLists.txt rename components/{common => modules}/Kconfig (65%) rename components/{common => modules/ai}/app_common.cpp (83%) rename components/{common/include => modules/ai}/app_common.hpp (56%) rename components/{common/app_dl.cpp => modules/ai/who_cat_face_detection.cpp} (100%) create mode 100644 components/modules/ai/who_cat_face_detection.hpp create mode 100644 components/modules/ai/who_motion_detection.hpp rename components/{common/app_camera.cpp => modules/camera/who_camera.c} (68%) rename components/{common/include/app_define.h => modules/camera/who_camera.h} (60%) rename components/{common/wallpaper => modules/lcd}/wallpaper_128x240_rgb565.h (100%) rename components/{common/app_lcd.c => modules/lcd/who_lcd.c} (63%) create mode 100644 components/modules/lcd/who_lcd.h rename components/{common/app_led.c => modules/led/who_led.c} (98%) rename components/{common/include/app_led.h => modules/led/who_led.h} (100%) create mode 100644 components/modules/web/app_httpd.cpp create mode 100644 components/modules/web/app_httpd.hpp create mode 100644 components/modules/web/app_mdns.c create mode 100644 components/modules/web/app_mdns.h create mode 100644 components/modules/web/app_wifi.c create mode 100644 components/modules/web/app_wifi.h create mode 100755 components/modules/web/www/compress_pages.sh create mode 100644 components/modules/web/www/index_ov2640.html create mode 100644 components/modules/web/www/index_ov2640.html.gz create mode 100644 components/modules/web/www/index_ov3660.html create mode 100644 components/modules/web/www/index_ov3660.html.gz create mode 100644 components/modules/web/www/index_ov5640.html create mode 100644 components/modules/web/www/index_ov5640.html.gz create mode 100755 components/modules/web/www/monitor.html create mode 100755 components/modules/web/www/monitor.html.gz diff --git a/components/common/CMakeLists.txt b/components/common/CMakeLists.txt deleted file mode 100644 index f676e21..0000000 --- a/components/common/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRC_DIRS . INCLUDE_DIRS include wallpaper REQUIRES esp32-camera esp-dl bus screen) - -component_compile_options(-ffast-math -O3) diff --git a/components/common/include/app_camera.hpp b/components/common/include/app_camera.hpp deleted file mode 100644 index 2f23e17..0000000 --- a/components/common/include/app_camera.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "esp_camera.h" -#include "app_define.h" - -/** - * @brief Initialize camera - * - * @param pixformat One of - * - PIXFORMAT_RGB565 - * - PIXFORMAT_YUV422 - * - PIXFORMAT_GRAYSC - * - PIXFORMAT_JPEG - * - PIXFORMAT_RGB888 - * - PIXFORMAT_RAW - * - PIXFORMAT_RGB444 - * - PIXFORMAT_RGB555 - * @param frame_size One of - * - FRAMESIZE_96X96, // 96x96 - * - FRAMESIZE_QQVGA, // 160x120 - * - FRAMESIZE_QCIF, // 176x144 - * - FRAMESIZE_HQVGA, // 240x176 - * - FRAMESIZE_240X240, // 240x240 - * - FRAMESIZE_QVGA, // 320x240 - * - FRAMESIZE_CIF, // 400x296 - * - FRAMESIZE_HVGA, // 480x320 - * - FRAMESIZE_VGA, // 640x480 - * - FRAMESIZE_SVGA, // 800x600 - * - FRAMESIZE_XGA, // 1024x768 - * - FRAMESIZE_HD, // 1280x720 - * - FRAMESIZE_SXGA, // 1280x1024 - * - FRAMESIZE_UXGA, // 1600x1200 - * - FRAMESIZE_FHD, // 1920x1080 - * - FRAMESIZE_P_HD, // 720x1280 - * - FRAMESIZE_P_3MP, // 864x1536 - * - FRAMESIZE_QXGA, // 2048x1536 - * - FRAMESIZE_QHD, // 2560x1440 - * - FRAMESIZE_WQXGA, // 2560x1600 - * - FRAMESIZE_P_FHD, // 1080x1920 - * - FRAMESIZE_QSXGA, // 2560x1920 - * @param fb_count Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) - * @param jpeg_quality Quality of JPEG output. 0-63 lower means higher quality - */ -void app_camera_init(const pixformat_t pixel_fromat, const framesize_t frame_size, const uint8_t fb_count, const uint8_t jpeg_quality = 12); - -/** - * @brief Decode fb , - * - if fb->format == PIXFORMAT_RGB565, then return fb->buf - * - else, then return a new memory with RGB888, don't forget to free it - * - * @param fb - */ -void *app_camera_decode(camera_fb_t *fb); diff --git a/components/common/include/app_lcd.h b/components/common/include/app_lcd.h deleted file mode 100644 index cc3c5f1..0000000 --- a/components/common/include/app_lcd.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include "esp_log.h" -#include "screen_driver.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - esp_err_t app_lcd_init(); - void app_lcd_draw_wallpaper(); - void app_lcd_set_color(int color); - void app_lcd_draw_bitmap(uint16_t *image_ptr, const uint16_t image_height, const uint16_t image_width); - -#ifdef __cplusplus -} -#endif diff --git a/components/modules/CMakeLists.txt b/components/modules/CMakeLists.txt new file mode 100644 index 0000000..b06f3ea --- /dev/null +++ b/components/modules/CMakeLists.txt @@ -0,0 +1,31 @@ +set(embed_files "web/www/index_ov2640.html.gz" + "web/www/index_ov3660.html.gz" + "web/www/index_ov5640.html.gz" + "web/www/monitor.html.gz") + +set(src_dirs + ai + camera + lcd + led + web) + +set(include_dirs + ai + camera + lcd + led + web) + +set(requires esp32-camera + esp-dl + bus + screen + esp_http_server + nvs_flash + mdns + fb_gfx) + +idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires} EMBED_FILES ${embed_files}) + +component_compile_options(-ffast-math -O3) diff --git a/components/common/Kconfig b/components/modules/Kconfig similarity index 65% rename from components/common/Kconfig rename to components/modules/Kconfig index 675f6ae..35724cb 100644 --- a/components/common/Kconfig +++ b/components/modules/Kconfig @@ -1,30 +1,64 @@ menu "ESP-WHO General Configuration" - menu "Camera Configuration" - - choice PIXEL_FORMAT - bool "Select Camera Pixel Format" - default CAMERA_PIXEL_FORMAT_RGB565 - help - Select Camera Pixel Format. - - config CAMERA_PIXEL_FORMAT_RGB565 - bool "RGB565" - config CAMERA_PIXEL_FORMAT_YUV422 - bool "YUV422" - config CAMERA_PIXEL_FORMAT_GRAYSCALE - bool "GRAYSCALE" - config CAMERA_PIXEL_FORMAT_JPEG - bool "JPEG" - config CAMERA_PIXEL_FORMAT_RGB888 - bool "RGB888" - config CAMERA_PIXEL_FORMAT_RAW - bool "RAW" - config CAMERA_PIXEL_FORMAT_RGB444 - bool "RGB444" - config CAMERA_PIXEL_FORMAT_RGB555 - bool "RGB555" + + menu "WiFi Configuration" - endchoice + config ESP_HOST_NAME + string "Camera Host Name" + default "" + help + Hostname that the camera will advertise over mDNS. + + config ESP_WIFI_SSID + string "WiFi STA SSID" + default "" + help + WiFi SSID (network name) to connect to or empty for Off. + + config ESP_WIFI_PASSWORD + string "WiFi STA Password" + default "" + help + WiFi Password if WEP/WPA/WPA2 or empty if Open. + + config ESP_WIFI_AP_SSID + string "WiFi AP SSID" + default "ESP32-Camera" + help + AP SSID (network name) to create or empty for Off. + + config ESP_WIFI_AP_PASSWORD + string "WiFi AP Password" + default "" + help + AP password for WPA2 or empty for Open. + + config MAX_STA_CONN + int "Maximal STA connections" + default 1 + help + Max number of the STA connects to AP. + + config ESP_WIFI_AP_CHANNEL + string "WiFi AP Channel" + default "" + help + AP channel for better connection performance. + + config SERVER_IP + string "WiFi AP IP Address" + default "192.168.4.1" + help + IP address that the ESP will assign to it's AP interface. You can use this IP to connect to the camera after flashing. + + config ESP_MAXIMUM_RETRY + int "Maximum retry" + default 5 + help + Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent. + + endmenu + + menu "Camera Configuration" choice CAMERA_MODULE bool "Select Camera Pinout" @@ -36,6 +70,8 @@ menu "ESP-WHO General Configuration" bool "WROVER-KIT With OV2640 Module" config CAMERA_MODULE_ESP_EYE bool "ESP-EYE DevKit" + config CAMERA_MODULE_ESP_S2_KALUGA + bool "ESP32-S2-Kaluga-1 V1.3" config CAMERA_MODULE_ESP_S3_EYE bool "ESP-S3-EYE DevKit" config CAMERA_MODEL_ESP32_CAM_BOARD @@ -232,84 +268,4 @@ menu "ESP-WHO General Configuration" help Select the LEDC Channel (0-7) endmenu - - - - menu "DL Configuration" - - config DL_ENABLED - bool "Enable Deep-Learning Application" - default y - - choice DL_SELECT_APP - bool "Select Application" - depends on DL_ENABLED - default DL_HUMAN_FACE - - config DL_HUMAN_FACE - bool "Human Face" - config DL_CAT_FACE - bool "Cat Face" - config DL_HUMAN_HAND - bool "Human Hand" - endchoice - - # Human Face Series - choice DL_HUMAN_FACE_DETECTION_S1_MODEL - bool "Select Human Face Detection Stage-1 Model" - depends on DL_HUMAN_FACE - default DL_HUMAN_FACE_DETECTION_S1_MSR01 - - config DL_HUMAN_FACE_DETECTION_S1_MSR01 - bool "HumanFaceDetectMSR01" - endchoice - - config DL_HUMAN_FACE_DETECTION_S2_ENABLED - bool "Enable Human Face Detection Stage-2" - depends on DL_HUMAN_FACE - default y - - choice DL_HUMAN_FACE_DETECTION_S2_MODEL - bool "Select Human Face Detection Stage-2 Model" - depends on DL_HUMAN_FACE_DETECTION_S2_ENABLED - default DL_HUMAN_FACE_DETECTION_S2_MNP01 - - config DL_HUMAN_FACE_DETECTION_S2_MNP01 - bool "HumanFaceDetectMNP01" - endchoice - - - config DL_HUMAN_FACE_RECOGNITION_ENABLED - bool "Enable Human Face Recognition" - depends on DL_HUMAN_FACE_DETECTION_S2_ENABLED - default y - - choice DL_HUMAN_FACE_RECOGNITION_MODEL - bool "Select Human Face Recognition Model" - depends on DL_HUMAN_FACE_RECOGNITION_ENABLED - default DL_HUMAN_FACE_RECOGNITION_XXX - - config DL_HUMAN_FACE_RECOGNITION_XXX - bool "HumanFaceRecognizeXXX" - endchoice - - # Cat Face Series - choice DL_CAT_FACE_DETECTION_MODEL - bool "Select Cat Face Detection Model" - depends on DL_CAT_FACE - default DL_CAT_FACE_DETECTION_MN03 - - config DL_CAT_FACE_DETECTION_MN03 - bool "CatFaceDetectMN03" - endchoice - - - # Human Hand Series - - - config DL_MOVING_TARGET_DETECTION_ENABLED - bool "Enable Moving Target Detection" - default y - - endmenu endmenu \ No newline at end of file diff --git a/components/common/app_common.cpp b/components/modules/ai/app_common.cpp similarity index 83% rename from components/common/app_common.cpp rename to components/modules/ai/app_common.cpp index eb07fa9..4c3cf49 100644 --- a/components/common/app_common.cpp +++ b/components/modules/ai/app_common.cpp @@ -1,7 +1,10 @@ #include "app_common.hpp" -#include "dl_image.hpp" #include "esp_log.h" +#include "esp_camera.h" + +#include "dl_image.hpp" + void draw_detection_result(uint16_t *image_ptr, int image_height, int image_width, std::list &results) { @@ -52,7 +55,7 @@ void print_detection_result(std::list &results) { ESP_LOGI("detection_result", "[%d]: (%3d, %3d, %3d, %3d)" #if CONFIG_DL_HUMAN_FACE_DETECTION_S2_ENABLED - " | left eye: (%3d, %3d), right eye: (%3d, %3d), nose: (%3d, %3d), mouth left: (%3d, %3d), mouth right: (%3d, %3d)" + " | left eye: (%3d, %3d), right eye: (%3d, %3d), nose: (%3d, %3d), mouth left: (%3d, %3d), mouth right: (%3d, %3d)" #endif , i, @@ -67,4 +70,33 @@ void print_detection_result(std::list &results) #endif ); } +} + +void *app_camera_decode(camera_fb_t *fb) +{ + if (fb->format == PIXFORMAT_RGB565) + { + return (void *)fb->buf; + } + else + { + uint8_t *image_ptr = (uint8_t *)malloc(fb->height * fb->width * 3 * sizeof(uint8_t)); + if (image_ptr) + { + if (fmt2rgb888(fb->buf, fb->len, fb->format, image_ptr)) + { + return (void *)image_ptr; + } + else + { + ESP_LOGE(TAG, "fmt2rgb888 failed"); + dl::tool::free_aligned(image_ptr); + } + } + else + { + ESP_LOGE(TAG, "malloc memory for image rgb888 failed"); + } + } + return NULL; } \ No newline at end of file diff --git a/components/common/include/app_common.hpp b/components/modules/ai/app_common.hpp similarity index 56% rename from components/common/include/app_common.hpp rename to components/modules/ai/app_common.hpp index 6f344e3..216414e 100644 --- a/components/common/include/app_common.hpp +++ b/components/modules/ai/app_common.hpp @@ -2,7 +2,25 @@ #include #include "dl_detect_define.hpp" -#include "app_define.h" +#include "esp_camera.h" +// #include "who_define.h" + +#if CONFIG_CAMERA_PIXEL_FORMAT_RGB565 +#define IMAGE_T uint16_t +#define COLOR_RED 0b0000000011111000 +#define COLOR_GREEN 0b1110000000000111 +#define COLOR_BLUE 0b0001111100000000 +#define COLOR_BLACK 0b0000000000000000 +#else +#define IMAGE_T uint8_t +#define COLOR_RED 0x0000FF +#define COLOR_GREEN 0x00FF00 +#define COLOR_BLUE 0xFF0000 +#define COLOR_BLACK 0x000000 +#endif + +static const char *TAG = "app_common"; + /** * @brief Draw detection result on RGB565 image. @@ -31,3 +49,12 @@ void draw_detection_result(uint8_t *image_ptr, int image_height, int image_width * @param results detection results */ void print_detection_result(std::list &results); + +/** + * @brief Decode fb , + * - if fb->format == PIXFORMAT_RGB565, then return fb->buf + * - else, then return a new memory with RGB888, don't forget to free it + * + * @param fb + */ +void *app_camera_decode(camera_fb_t *fb); diff --git a/components/common/app_dl.cpp b/components/modules/ai/who_cat_face_detection.cpp similarity index 100% rename from components/common/app_dl.cpp rename to components/modules/ai/who_cat_face_detection.cpp diff --git a/components/modules/ai/who_cat_face_detection.hpp b/components/modules/ai/who_cat_face_detection.hpp new file mode 100644 index 0000000..9da6717 --- /dev/null +++ b/components/modules/ai/who_cat_face_detection.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +void register_cat_face_detection(QueueHandle_t frame_i, QueueHandle_t event, + QueueHandle_t result, QueueHandle_t frame_o = NULL); diff --git a/components/modules/ai/who_motion_detection.hpp b/components/modules/ai/who_motion_detection.hpp new file mode 100644 index 0000000..88a219f --- /dev/null +++ b/components/modules/ai/who_motion_detection.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +void register_motion_detection(QueueHandle_t frame_i, QueueHandle_t event, + QueueHandle_t result, QueueHandle_t frame_o = NULL); diff --git a/components/common/app_camera.cpp b/components/modules/camera/who_camera.c similarity index 68% rename from components/common/app_camera.cpp rename to components/modules/camera/who_camera.c index 65823a1..c6d8a8d 100644 --- a/components/common/app_camera.cpp +++ b/components/modules/camera/who_camera.c @@ -1,14 +1,25 @@ -#include "app_camera.hpp" +#include "who_camera.h" #include "esp_log.h" #include "esp_system.h" -#include "app_define.h" -#include "dl_tool.hpp" +static const char *TAG = "who_camera"; +static QueueHandle_t xQueueFrameO = NULL; -static const char *TAG = "app_camera"; +static void task_process_handler(void *arg) +{ + while (true) + { + camera_fb_t *frame = esp_camera_fb_get(); + if (frame) + xQueueSend(xQueueFrameO, &frame, portMAX_DELAY); + } +} -void app_camera_init(const pixformat_t pixel_fromat, const framesize_t frame_size, const uint8_t fb_count, const uint8_t jpeg_quality) +void register_camera(const pixformat_t pixel_fromat, + const framesize_t frame_size, + const uint8_t fb_count, + const QueueHandle_t frame_o) { ESP_LOGI(TAG, "Camera module is %s", CAMERA_MODULE_NAME); @@ -49,8 +60,9 @@ void app_camera_init(const pixformat_t pixel_fromat, const framesize_t frame_siz config.xclk_freq_hz = XCLK_FREQ_HZ; config.pixel_format = pixel_fromat; config.frame_size = frame_size; - config.jpeg_quality = jpeg_quality; + config.jpeg_quality = 12; config.fb_count = fb_count; + config.fb_location = CAMERA_FB_IN_PSRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // camera init @@ -69,33 +81,7 @@ void app_camera_init(const pixformat_t pixel_fromat, const framesize_t frame_siz s->set_brightness(s, 1); //up the blightness just a bit s->set_saturation(s, -2); //lower the saturation } -} -void *app_camera_decode(camera_fb_t *fb) -{ - if (fb->format == PIXFORMAT_RGB565) - { - return (void *)fb->buf; - } - else - { - uint8_t *image_ptr = (uint8_t *)malloc(fb->height * fb->width * 3 * sizeof(uint8_t)); - if (image_ptr) - { - if (fmt2rgb888(fb->buf, fb->len, fb->format, image_ptr)) - { - return (void *)image_ptr; - } - else - { - ESP_LOGE(TAG, "fmt2rgb888 failed"); - dl::tool::free_aligned(image_ptr); - } - } - else - { - ESP_LOGE(TAG, "malloc memory for image rgb888 failed"); - } - } - return NULL; + xQueueFrameO = frame_o; + xTaskCreatePinnedToCore(task_process_handler, TAG, 2 * 1024, NULL, 5, NULL, 1); } diff --git a/components/common/include/app_define.h b/components/modules/camera/who_camera.h similarity index 60% rename from components/common/include/app_define.h rename to components/modules/camera/who_camera.h index fc62241..188bd9c 100644 --- a/components/common/include/app_define.h +++ b/components/modules/camera/who_camera.h @@ -1,22 +1,11 @@ #pragma once -#include -#include "esp_camera.h" -#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" -#if CONFIG_CAMERA_PIXEL_FORMAT_RGB565 -#define IMAGE_T uint16_t -#define COLOR_RED 0b0000000011111000 -#define COLOR_GREEN 0b1110000000000111 -#define COLOR_BLUE 0b0001111100000000 -#define COLOR_BLACK 0b0000000000000000 -#else -#define IMAGE_T uint8_t -#define COLOR_RED 0x0000FF -#define COLOR_GREEN 0x00FF00 -#define COLOR_BLUE 0xFF0000 -#define COLOR_BLACK 0x000000 -#endif +#include "esp_camera.h" #if CONFIG_CAMERA_MODULE_WROVER_KIT #define CAMERA_MODULE_NAME "Wrover Kit" @@ -58,10 +47,30 @@ #define CAMERA_PIN_HREF 27 #define CAMERA_PIN_PCLK 25 +#elif CONFIG_CAMERA_MODULE_ESP_S2_KALUGA +#define CAMERA_MODULE_NAME "ESP-S2-KALUGA" +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 +#define CAMERA_PIN_XCLK 1 +#define CAMERA_PIN_SIOD 8 +#define CAMERA_PIN_SIOC 7 + +#define CAMERA_PIN_D7 38 +#define CAMERA_PIN_D6 21 +#define CAMERA_PIN_D5 40 +#define CAMERA_PIN_D4 39 +#define CAMERA_PIN_D3 42 +#define CAMERA_PIN_D2 41 +#define CAMERA_PIN_D1 37 +#define CAMERA_PIN_D0 36 +#define CAMERA_PIN_VSYNC 2 +#define CAMERA_PIN_HREF 3 +#define CAMERA_PIN_PCLK 33 + #elif CONFIG_CAMERA_MODULE_ESP_S3_EYE #define CAMERA_MODULE_NAME "ESP-S3-EYE" -#define CAMERA_PIN_PWDN 43 -#define CAMERA_PIN_RESET 44 +#define CAMERA_PIN_PWDN -1 +#define CAMERA_PIN_RESET -1 #define CAMERA_PIN_VSYNC 6 #define CAMERA_PIN_HREF 7 @@ -185,27 +194,52 @@ #define XCLK_FREQ_HZ 20000000 -#if CONFIG_CAMERA_PIXEL_FORMAT_RGB565 -#define CAMERA_PIXEL_FORMAT PIXFORMAT_RGB565 +#ifdef __cplusplus +extern "C" +{ #endif -#if CONFIG_CAMERA_PIXEL_FORMAT_YUV422 -#define CAMERA_PIXEL_FORMAT PIXFORMAT_YUV422 -#endif -#if CONFIG_CAMERA_PIXEL_FORMAT_GRAYSCALE -#define CAMERA_PIXEL_FORMAT PIXFORMAT_GRAYSCALE -#endif -#if CONFIG_CAMERA_PIXEL_FORMAT_JPEG -#define CAMERA_PIXEL_FORMAT PIXFORMAT_JPEG -#endif -#if CONFIG_CAMERA_PIXEL_FORMAT_RGB888 -#define CAMERA_PIXEL_FORMAT PIXFORMAT_RGB888 -#endif -#if CONFIG_CAMERA_PIXEL_FORMAT_RAW -#define CAMERA_PIXEL_FORMAT PIXFORMAT_RAW -#endif -#if CONFIG_CAMERA_PIXEL_FORMAT_RGB444 -#define CAMERA_PIXEL_FORMAT PIXFORMAT_RGB444 -#endif -#if CONFIG_CAMERA_PIXEL_FORMAT_RGB555 -#define CAMERA_PIXEL_FORMAT PIXFORMAT_RGB555 + /** + * @brief Initialize camera + * + * @param pixformat One of + * - PIXFORMAT_RGB565 + * - PIXFORMAT_YUV422 + * - PIXFORMAT_GRAYSC + * - PIXFORMAT_JPEG + * - PIXFORMAT_RGB888 + * - PIXFORMAT_RAW + * - PIXFORMAT_RGB444 + * - PIXFORMAT_RGB555 + * @param frame_size One of + * - FRAMESIZE_96X96, // 96x96 + * - FRAMESIZE_QQVGA, // 160x120 + * - FRAMESIZE_QCIF, // 176x144 + * - FRAMESIZE_HQVGA, // 240x176 + * - FRAMESIZE_240X240, // 240x240 + * - FRAMESIZE_QVGA, // 320x240 + * - FRAMESIZE_CIF, // 400x296 + * - FRAMESIZE_HVGA, // 480x320 + * - FRAMESIZE_VGA, // 640x480 + * - FRAMESIZE_SVGA, // 800x600 + * - FRAMESIZE_XGA, // 1024x768 + * - FRAMESIZE_HD, // 1280x720 + * - FRAMESIZE_SXGA, // 1280x1024 + * - FRAMESIZE_UXGA, // 1600x1200 + * - FRAMESIZE_FHD, // 1920x1080 + * - FRAMESIZE_P_HD, // 720x1280 + * - FRAMESIZE_P_3MP, // 864x1536 + * - FRAMESIZE_QXGA, // 2048x1536 + * - FRAMESIZE_QHD, // 2560x1440 + * - FRAMESIZE_WQXGA, // 2560x1600 + * - FRAMESIZE_P_FHD, // 1080x1920 + * - FRAMESIZE_QSXGA, // 2560x1920 + * @param fb_count Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed) + */ + void register_camera(const pixformat_t pixel_fromat, + const framesize_t frame_size, + const uint8_t fb_count, + const QueueHandle_t frame_o); + +#ifdef __cplusplus +} #endif diff --git a/components/common/wallpaper/wallpaper_128x240_rgb565.h b/components/modules/lcd/wallpaper_128x240_rgb565.h similarity index 100% rename from components/common/wallpaper/wallpaper_128x240_rgb565.h rename to components/modules/lcd/wallpaper_128x240_rgb565.h diff --git a/components/common/app_lcd.c b/components/modules/lcd/who_lcd.c similarity index 63% rename from components/common/app_lcd.c rename to components/modules/lcd/who_lcd.c index 3eee8f5..fa4d3ff 100644 --- a/components/common/app_lcd.c +++ b/components/modules/lcd/who_lcd.c @@ -1,26 +1,57 @@ -#include "app_lcd.h" +#include "who_lcd.h" +#include "esp_camera.h" #include #include "wallpaper_128x240_rgb565.h" -static const char *TAG = "app_lcd"; +static const char *TAG = "who_lcd"; static scr_driver_t g_lcd; static scr_info_t g_lcd_info; -esp_err_t app_lcd_init() +static QueueHandle_t xQueueFrameI = NULL; +static QueueHandle_t xQueueFrameO = NULL; +static bool gReturnFB = true; + +static void task_process_handler(void *arg) +{ + camera_fb_t *frame = NULL; + + while (true) + { + if (xQueueReceive(xQueueFrameI, &frame, portMAX_DELAY)) + { + g_lcd.draw_bitmap(0, 0, frame->width, frame->height, (uint16_t *)frame->buf); + + if (xQueueFrameO) + { + xQueueSend(xQueueFrameO, &frame, portMAX_DELAY); + } + else if (gReturnFB) + { + esp_camera_fb_return(frame); + } + else + { + free(frame); + } + } + } +} + +esp_err_t register_lcd(const QueueHandle_t frame_i, const QueueHandle_t frame_o, const bool return_fb) { spi_config_t bus_conf = { - .miso_io_num = -1, - .mosi_io_num = 48, - .sclk_io_num = 21, + .miso_io_num = BOARD_LCD_MISO, + .mosi_io_num = BOARD_LCD_MOSI, + .sclk_io_num = BOARD_LCD_SCK, .max_transfer_sz = 2 * 240 * 240 + 10, }; spi_bus_handle_t spi_bus = spi_bus_create(SPI2_HOST, &bus_conf); scr_interface_spi_config_t spi_lcd_cfg = { .spi_bus = spi_bus, - .pin_num_cs = 43, - .pin_num_dc = 47, + .pin_num_cs = BOARD_LCD_CS, + .pin_num_dc = BOARD_LCD_DC, .clk_freq = 40 * 1000000, .swap_data = 0, }; @@ -36,10 +67,10 @@ esp_err_t app_lcd_init() scr_controller_config_t lcd_cfg = { .interface_drv = iface_drv, - .pin_num_rst = -1, - .pin_num_bckl = -1, + .pin_num_rst = BOARD_LCD_RST, + .pin_num_bckl = BOARD_LCD_BL, .rst_active_level = 0, - .bckl_active_level = 1, + .bckl_active_level = 0, .offset_hor = 0, .offset_ver = 0, .width = 240, @@ -55,6 +86,17 @@ esp_err_t app_lcd_init() g_lcd.get_info(&g_lcd_info); ESP_LOGI(TAG, "Screen name:%s | width:%d | height:%d", g_lcd_info.name, g_lcd_info.width, g_lcd_info.height); + + app_lcd_set_color(0x000000); + vTaskDelay(pdMS_TO_TICKS(500)); + app_lcd_draw_wallpaper(); + vTaskDelay(pdMS_TO_TICKS(500)); + + xQueueFrameI = frame_i; + xQueueFrameO = frame_o; + gReturnFB = return_fb; + xTaskCreatePinnedToCore(task_process_handler, TAG, 4 * 1024, NULL, 5, NULL, 1); + return ESP_OK; } @@ -104,8 +146,3 @@ void app_lcd_set_color(int color) free(buffer); } } - -void app_lcd_draw_bitmap(uint16_t *image_ptr, const uint16_t image_height, const uint16_t image_width) -{ - g_lcd.draw_bitmap(0, 0, image_width, image_height, image_ptr); -} \ No newline at end of file diff --git a/components/modules/lcd/who_lcd.h b/components/modules/lcd/who_lcd.h new file mode 100644 index 0000000..3b0c9da --- /dev/null +++ b/components/modules/lcd/who_lcd.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "esp_log.h" +#include "screen_driver.h" + +#define BOARD_LCD_MOSI 46 +#define BOARD_LCD_MISO -1 +#define BOARD_LCD_SCK 21 +#define BOARD_LCD_CS 44 +#define BOARD_LCD_DC 47 +#define BOARD_LCD_RST -1 +#define BOARD_LCD_BL 48 + +#ifdef __cplusplus +extern "C" +{ +#endif + + esp_err_t register_lcd(const QueueHandle_t frame_i, const QueueHandle_t frame_o, const bool return_fb); + + void app_lcd_draw_wallpaper(); + void app_lcd_set_color(int color); + +#ifdef __cplusplus +} +#endif diff --git a/components/common/app_led.c b/components/modules/led/who_led.c similarity index 98% rename from components/common/app_led.c rename to components/modules/led/who_led.c index 115c8c6..0fb3e3b 100644 --- a/components/common/app_led.c +++ b/components/modules/led/who_led.c @@ -1,4 +1,4 @@ -#include "app_led.h" +#include "who_led.h" #if CONFIG_LED_ILLUMINATOR_ENABLED #include "esp_log.h" diff --git a/components/common/include/app_led.h b/components/modules/led/who_led.h similarity index 100% rename from components/common/include/app_led.h rename to components/modules/led/who_led.h diff --git a/components/modules/web/app_httpd.cpp b/components/modules/web/app_httpd.cpp new file mode 100644 index 0000000..d571c9b --- /dev/null +++ b/components/modules/web/app_httpd.cpp @@ -0,0 +1,778 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "app_httpd.hpp" + +#include +#include "esp_http_server.h" +#include "esp_timer.h" +#include "img_converters.h" +#include "fb_gfx.h" +#include "app_mdns.h" +#include "sdkconfig.h" + +#include "who_camera.h" + +#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) +#include "esp32-hal-log.h" +#define TAG "" +#else +#include "esp_log.h" +static const char *TAG = "camera_httpd"; +#endif + +static QueueHandle_t xQueueFrameI = NULL; +static QueueHandle_t xQueueFrameO = NULL; +static bool gReturnFB = true; + +static int8_t detection_enabled = 0; +static int8_t recognition_enabled = 0; +static int8_t is_enrolling = 0; + +typedef struct +{ + httpd_req_t *req; + size_t len; +} jpg_chunking_t; + +#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\nX-Timestamp: %d.%06d\r\n\r\n"; + +httpd_handle_t stream_httpd = NULL; +httpd_handle_t camera_httpd = NULL; + +static size_t jpg_encode_stream(void *arg, size_t index, const void *data, size_t len) +{ + jpg_chunking_t *j = (jpg_chunking_t *)arg; + if (!index) + { + j->len = 0; + } + if (httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK) + { + return 0; + } + j->len += len; + return len; +} + +static esp_err_t capture_handler(httpd_req_t *req) +{ + camera_fb_t *frame = NULL; + esp_err_t res = ESP_OK; + + if (xQueueReceive(xQueueFrameI, &frame, portMAX_DELAY)) + { + httpd_resp_set_type(req, "image/jpeg"); + httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + + char ts[32]; + snprintf(ts, 32, "%ld.%06ld", frame->timestamp.tv_sec, frame->timestamp.tv_usec); + httpd_resp_set_hdr(req, "X-Timestamp", (const char *)ts); + + // size_t fb_len = 0; + if (frame->format == PIXFORMAT_JPEG) + { + // fb_len = frame->len; + res = httpd_resp_send(req, (const char *)frame->buf, frame->len); + } + else + { + jpg_chunking_t jchunk = {req, 0}; + res = frame2jpg_cb(frame, 80, jpg_encode_stream, &jchunk) ? ESP_OK : ESP_FAIL; + httpd_resp_send_chunk(req, NULL, 0); + // fb_len = jchunk.len; + } + + if (xQueueFrameO) + { + xQueueSend(xQueueFrameO, &frame, portMAX_DELAY); + } + else if (gReturnFB) + { + esp_camera_fb_return(frame); + } + else + { + free(frame); + } + } + else + { + ESP_LOGE(TAG, "Camera capture failed"); + httpd_resp_send_500(req); + return ESP_FAIL; + } + + return res; +} + +static esp_err_t stream_handler(httpd_req_t *req) +{ + camera_fb_t *frame = NULL; + struct timeval _timestamp; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len = 0; + uint8_t *_jpg_buf = NULL; + char *part_buf[128]; + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if (res != ESP_OK) + { + return res; + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + httpd_resp_set_hdr(req, "X-Framerate", "60"); + + while (true) + { + if (xQueueReceive(xQueueFrameI, &frame, portMAX_DELAY)) + { + _timestamp.tv_sec = frame->timestamp.tv_sec; + _timestamp.tv_usec = frame->timestamp.tv_usec; + + if (frame->format == PIXFORMAT_JPEG) + { + _jpg_buf = frame->buf; + _jpg_buf_len = frame->len; + } + else if (!frame2jpg(frame, 80, &_jpg_buf, &_jpg_buf_len)) + { + ESP_LOGE(TAG, "JPEG compression failed"); + res = ESP_FAIL; + } + } + else + { + res = ESP_FAIL; + } + + 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, 128, _STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec); + 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 (frame->format != PIXFORMAT_JPEG) + { + free(_jpg_buf); + _jpg_buf = NULL; + } + + if (xQueueFrameO) + { + xQueueSend(xQueueFrameO, &frame, portMAX_DELAY); + } + else if (gReturnFB) + { + esp_camera_fb_return(frame); + } + else + { + free(frame); + } + + if (res != ESP_OK) + { + break; + } + } + + return res; +} + +static esp_err_t parse_get(httpd_req_t *req, char **obuf) +{ + char *buf = NULL; + size_t buf_len = 0; + + buf_len = httpd_req_get_url_query_len(req) + 1; + if (buf_len > 1) + { + buf = (char *)malloc(buf_len); + if (!buf) + { + httpd_resp_send_500(req); + return ESP_FAIL; + } + if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) + { + *obuf = buf; + return ESP_OK; + } + free(buf); + } + httpd_resp_send_404(req); + return ESP_FAIL; +} + +static esp_err_t cmd_handler(httpd_req_t *req) +{ + char *buf = NULL; + char variable[32]; + char value[32]; + + if (parse_get(req, &buf) != ESP_OK || + httpd_query_key_value(buf, "var", variable, sizeof(variable)) != ESP_OK || + httpd_query_key_value(buf, "val", value, sizeof(value)) != ESP_OK) + { + free(buf); + httpd_resp_send_404(req); + return ESP_FAIL; + } + free(buf); + + int val = atoi(value); + ESP_LOGI(TAG, "%s = %d", variable, val); + sensor_t *s = esp_camera_sensor_get(); + int res = 0; + + if (!strcmp(variable, "framesize")) + { + if (s->pixformat == PIXFORMAT_JPEG) + { + res = s->set_framesize(s, (framesize_t)val); + if (res == 0) + { + app_mdns_update_framesize(val); + } + } + } + else if (!strcmp(variable, "quality")) + res = s->set_quality(s, val); + else if (!strcmp(variable, "contrast")) + res = s->set_contrast(s, val); + else if (!strcmp(variable, "brightness")) + res = s->set_brightness(s, val); + else if (!strcmp(variable, "saturation")) + res = s->set_saturation(s, val); + else if (!strcmp(variable, "gainceiling")) + res = s->set_gainceiling(s, (gainceiling_t)val); + else if (!strcmp(variable, "colorbar")) + res = s->set_colorbar(s, val); + else if (!strcmp(variable, "awb")) + res = s->set_whitebal(s, val); + else if (!strcmp(variable, "agc")) + res = s->set_gain_ctrl(s, val); + else if (!strcmp(variable, "aec")) + res = s->set_exposure_ctrl(s, val); + else if (!strcmp(variable, "hmirror")) + res = s->set_hmirror(s, val); + else if (!strcmp(variable, "vflip")) + res = s->set_vflip(s, val); + else if (!strcmp(variable, "awb_gain")) + res = s->set_awb_gain(s, val); + else if (!strcmp(variable, "agc_gain")) + res = s->set_agc_gain(s, val); + else if (!strcmp(variable, "aec_value")) + res = s->set_aec_value(s, val); + else if (!strcmp(variable, "aec2")) + res = s->set_aec2(s, val); + else if (!strcmp(variable, "dcw")) + res = s->set_dcw(s, val); + else if (!strcmp(variable, "bpc")) + res = s->set_bpc(s, val); + else if (!strcmp(variable, "wpc")) + res = s->set_wpc(s, val); + else if (!strcmp(variable, "raw_gma")) + res = s->set_raw_gma(s, val); + else if (!strcmp(variable, "lenc")) + res = s->set_lenc(s, val); + else if (!strcmp(variable, "special_effect")) + res = s->set_special_effect(s, val); + else if (!strcmp(variable, "wb_mode")) + res = s->set_wb_mode(s, val); + else if (!strcmp(variable, "ae_level")) + res = s->set_ae_level(s, val); +#ifdef CONFIG_LED_ILLUMINATOR_ENABLED + else if (!strcmp(variable, "led_intensity")) + led_duty = val; +#endif + + else if (!strcmp(variable, "face_detect")) + { + detection_enabled = val; + if (!detection_enabled) + { + recognition_enabled = 0; + } + } + else if (!strcmp(variable, "face_enroll")) + is_enrolling = val; + else if (!strcmp(variable, "face_recognize")) + { + recognition_enabled = val; + if (recognition_enabled) + { + detection_enabled = val; + } + } + else + { + res = -1; + } + + if (res) + { + return httpd_resp_send_500(req); + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, NULL, 0); +} + +static int print_reg(char *p, sensor_t *s, uint16_t reg, uint32_t mask) +{ + return sprintf(p, "\"0x%x\":%u,", reg, s->get_reg(s, reg, mask)); +} + +static esp_err_t status_handler(httpd_req_t *req) +{ + static char json_response[1024]; + + sensor_t *s = esp_camera_sensor_get(); + char *p = json_response; + *p++ = '{'; + + if (s->id.PID == OV5640_PID || s->id.PID == OV3660_PID) + { + for (int reg = 0x3400; reg < 0x3406; reg += 2) + { + p += print_reg(p, s, reg, 0xFFF); //12 bit + } + p += print_reg(p, s, 0x3406, 0xFF); + + p += print_reg(p, s, 0x3500, 0xFFFF0); //16 bit + p += print_reg(p, s, 0x3503, 0xFF); + p += print_reg(p, s, 0x350a, 0x3FF); //10 bit + p += print_reg(p, s, 0x350c, 0xFFFF); //16 bit + + for (int reg = 0x5480; reg <= 0x5490; reg++) + { + p += print_reg(p, s, reg, 0xFF); + } + + for (int reg = 0x5380; reg <= 0x538b; reg++) + { + p += print_reg(p, s, reg, 0xFF); + } + + for (int reg = 0x5580; reg < 0x558a; reg++) + { + p += print_reg(p, s, reg, 0xFF); + } + p += print_reg(p, s, 0x558a, 0x1FF); //9 bit + } + else + { + p += print_reg(p, s, 0xd3, 0xFF); + p += print_reg(p, s, 0x111, 0xFF); + p += print_reg(p, s, 0x132, 0xFF); + } + + p += sprintf(p, "\"board\":\"%s\",", CAMERA_MODULE_NAME); + p += sprintf(p, "\"xclk\":%u,", s->xclk_freq_hz / 1000000); + p += sprintf(p, "\"pixformat\":%u,", s->pixformat); + p += sprintf(p, "\"framesize\":%u,", s->status.framesize); + p += sprintf(p, "\"quality\":%u,", s->status.quality); + p += sprintf(p, "\"brightness\":%d,", s->status.brightness); + p += sprintf(p, "\"contrast\":%d,", s->status.contrast); + p += sprintf(p, "\"saturation\":%d,", s->status.saturation); + p += sprintf(p, "\"sharpness\":%d,", s->status.sharpness); + p += sprintf(p, "\"special_effect\":%u,", s->status.special_effect); + p += sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode); + p += sprintf(p, "\"awb\":%u,", s->status.awb); + p += sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain); + p += sprintf(p, "\"aec\":%u,", s->status.aec); + p += sprintf(p, "\"aec2\":%u,", s->status.aec2); + p += sprintf(p, "\"ae_level\":%d,", s->status.ae_level); + p += sprintf(p, "\"aec_value\":%u,", s->status.aec_value); + p += sprintf(p, "\"agc\":%u,", s->status.agc); + p += sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain); + p += sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling); + p += sprintf(p, "\"bpc\":%u,", s->status.bpc); + p += sprintf(p, "\"wpc\":%u,", s->status.wpc); + p += sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma); + p += sprintf(p, "\"lenc\":%u,", s->status.lenc); + p += sprintf(p, "\"hmirror\":%u,", s->status.hmirror); + p += sprintf(p, "\"dcw\":%u,", s->status.dcw); + p += sprintf(p, "\"colorbar\":%u", s->status.colorbar); +#ifdef CONFIG_LED_ILLUMINATOR_ENABLED + p += sprintf(p, ",\"led_intensity\":%u", led_duty); +#else + p += sprintf(p, ",\"led_intensity\":%d", -1); +#endif + p += sprintf(p, ",\"face_detect\":%u", detection_enabled); + p += sprintf(p, ",\"face_enroll\":%u,", is_enrolling); + p += sprintf(p, "\"face_recognize\":%u", recognition_enabled); + *p++ = '}'; + *p++ = 0; + httpd_resp_set_type(req, "application/json"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, json_response, strlen(json_response)); +} + +static esp_err_t mdns_handler(httpd_req_t *req) +{ + size_t json_len = 0; + const char *json_response = app_mdns_query(&json_len); + httpd_resp_set_type(req, "application/json"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, json_response, json_len); +} + +static esp_err_t xclk_handler(httpd_req_t *req) +{ + char *buf = NULL; + char _xclk[32]; + + if (parse_get(req, &buf) != ESP_OK || + httpd_query_key_value(buf, "xclk", _xclk, sizeof(_xclk)) != ESP_OK) + { + free(buf); + httpd_resp_send_404(req); + return ESP_FAIL; + } + free(buf); + + int xclk = atoi(_xclk); + ESP_LOGI(TAG, "Set XCLK: %d MHz", xclk); + + sensor_t *s = esp_camera_sensor_get(); + int res = s->set_xclk(s, LEDC_TIMER_0, xclk); + if (res) + { + return httpd_resp_send_500(req); + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, NULL, 0); +} + +static esp_err_t reg_handler(httpd_req_t *req) +{ + char *buf = NULL; + char _reg[32]; + char _mask[32]; + char _val[32]; + + if (parse_get(req, &buf) != ESP_OK || + httpd_query_key_value(buf, "reg", _reg, sizeof(_reg)) != ESP_OK || + httpd_query_key_value(buf, "mask", _mask, sizeof(_mask)) != ESP_OK || + httpd_query_key_value(buf, "val", _val, sizeof(_val)) != ESP_OK) + { + free(buf); + httpd_resp_send_404(req); + return ESP_FAIL; + } + free(buf); + + int reg = atoi(_reg); + int mask = atoi(_mask); + int val = atoi(_val); + ESP_LOGI(TAG, "Set Register: reg: 0x%02x, mask: 0x%02x, value: 0x%02x", reg, mask, val); + + sensor_t *s = esp_camera_sensor_get(); + int res = s->set_reg(s, reg, mask, val); + if (res) + { + return httpd_resp_send_500(req); + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, NULL, 0); +} + +static esp_err_t greg_handler(httpd_req_t *req) +{ + char *buf = NULL; + char _reg[32]; + char _mask[32]; + + if (parse_get(req, &buf) != ESP_OK || + httpd_query_key_value(buf, "reg", _reg, sizeof(_reg)) != ESP_OK || + httpd_query_key_value(buf, "mask", _mask, sizeof(_mask)) != ESP_OK) + { + free(buf); + httpd_resp_send_404(req); + return ESP_FAIL; + } + free(buf); + + int reg = atoi(_reg); + int mask = atoi(_mask); + sensor_t *s = esp_camera_sensor_get(); + int res = s->get_reg(s, reg, mask); + if (res < 0) + { + return httpd_resp_send_500(req); + } + ESP_LOGI(TAG, "Get Register: reg: 0x%02x, mask: 0x%02x, value: 0x%02x", reg, mask, res); + + char buffer[20]; + const char *val = itoa(res, buffer, 10); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, val, strlen(val)); +} + +static int parse_get_var(char *buf, const char *key, int def) +{ + char _int[16]; + if (httpd_query_key_value(buf, key, _int, sizeof(_int)) != ESP_OK) + { + return def; + } + return atoi(_int); +} + +static esp_err_t pll_handler(httpd_req_t *req) +{ + char *buf = NULL; + + if (parse_get(req, &buf) != ESP_OK) + { + free(buf); + httpd_resp_send_404(req); + return ESP_FAIL; + } + + int bypass = parse_get_var(buf, "bypass", 0); + int mul = parse_get_var(buf, "mul", 0); + int sys = parse_get_var(buf, "sys", 0); + int root = parse_get_var(buf, "root", 0); + int pre = parse_get_var(buf, "pre", 0); + int seld5 = parse_get_var(buf, "seld5", 0); + int pclken = parse_get_var(buf, "pclken", 0); + int pclk = parse_get_var(buf, "pclk", 0); + free(buf); + + ESP_LOGI(TAG, "Set Pll: bypass: %d, mul: %d, sys: %d, root: %d, pre: %d, seld5: %d, pclken: %d, pclk: %d", bypass, mul, sys, root, pre, seld5, pclken, pclk); + sensor_t *s = esp_camera_sensor_get(); + int res = s->set_pll(s, bypass, mul, sys, root, pre, seld5, pclken, pclk); + if (res) + { + return httpd_resp_send_500(req); + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, NULL, 0); +} + +static esp_err_t win_handler(httpd_req_t *req) +{ + char *buf = NULL; + + if (parse_get(req, &buf) != ESP_OK) + { + free(buf); + httpd_resp_send_404(req); + return ESP_FAIL; + } + + int startX = parse_get_var(buf, "sx", 0); + int startY = parse_get_var(buf, "sy", 0); + int endX = parse_get_var(buf, "ex", 0); + int endY = parse_get_var(buf, "ey", 0); + int offsetX = parse_get_var(buf, "offx", 0); + int offsetY = parse_get_var(buf, "offy", 0); + int totalX = parse_get_var(buf, "tx", 0); + int totalY = parse_get_var(buf, "ty", 0); + int outputX = parse_get_var(buf, "ox", 0); + int outputY = parse_get_var(buf, "oy", 0); + bool scale = parse_get_var(buf, "scale", 0) == 1; + bool binning = parse_get_var(buf, "binning", 0) == 1; + free(buf); + + ESP_LOGI(TAG, "Set Window: Start: %d %d, End: %d %d, Offset: %d %d, Total: %d %d, Output: %d %d, Scale: %u, Binning: %u", startX, startY, endX, endY, offsetX, offsetY, totalX, totalY, outputX, outputY, scale, binning); + sensor_t *s = esp_camera_sensor_get(); + int res = s->set_res_raw(s, startX, startY, endX, endY, offsetX, offsetY, totalX, totalY, outputX, outputY, scale, binning); + if (res) + { + return httpd_resp_send_500(req); + } + + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, NULL, 0); +} + +static esp_err_t index_handler(httpd_req_t *req) +{ + extern const unsigned char index_ov2640_html_gz_start[] asm("_binary_index_ov2640_html_gz_start"); + extern const unsigned char index_ov2640_html_gz_end[] asm("_binary_index_ov2640_html_gz_end"); + size_t index_ov2640_html_gz_len = index_ov2640_html_gz_end - index_ov2640_html_gz_start; + + extern const unsigned char index_ov3660_html_gz_start[] asm("_binary_index_ov3660_html_gz_start"); + extern const unsigned char index_ov3660_html_gz_end[] asm("_binary_index_ov3660_html_gz_end"); + size_t index_ov3660_html_gz_len = index_ov3660_html_gz_end - index_ov3660_html_gz_start; + + extern const unsigned char index_ov5640_html_gz_start[] asm("_binary_index_ov5640_html_gz_start"); + extern const unsigned char index_ov5640_html_gz_end[] asm("_binary_index_ov5640_html_gz_end"); + size_t index_ov5640_html_gz_len = index_ov5640_html_gz_end - index_ov5640_html_gz_start; + + httpd_resp_set_type(req, "text/html"); + httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); + sensor_t *s = esp_camera_sensor_get(); + if (s != NULL) + { + if (s->id.PID == OV3660_PID) + { + return httpd_resp_send(req, (const char *)index_ov3660_html_gz_start, index_ov3660_html_gz_len); + } + else if (s->id.PID == OV5640_PID) + { + return httpd_resp_send(req, (const char *)index_ov5640_html_gz_start, index_ov5640_html_gz_len); + } + else + { + return httpd_resp_send(req, (const char *)index_ov2640_html_gz_start, index_ov2640_html_gz_len); + } + } + else + { + ESP_LOGE(TAG, "Camera sensor not found"); + return httpd_resp_send_500(req); + } +} + +static esp_err_t monitor_handler(httpd_req_t *req) +{ + extern const unsigned char monitor_html_gz_start[] asm("_binary_monitor_html_gz_start"); + extern const unsigned char monitor_html_gz_end[] asm("_binary_monitor_html_gz_end"); + size_t monitor_html_gz_len = monitor_html_gz_end - monitor_html_gz_start; + httpd_resp_set_type(req, "text/html"); + httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); + return httpd_resp_send(req, (const char *)monitor_html_gz_start, monitor_html_gz_len); +} + +void register_httpd(const QueueHandle_t frame_i, const QueueHandle_t frame_o, const bool return_fb) +{ + xQueueFrameI = frame_i; + xQueueFrameO = frame_o; + gReturnFB = return_fb; + + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.max_uri_handlers = 12; + + httpd_uri_t index_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = index_handler, + .user_ctx = NULL}; + + httpd_uri_t status_uri = { + .uri = "/status", + .method = HTTP_GET, + .handler = status_handler, + .user_ctx = NULL}; + + httpd_uri_t cmd_uri = { + .uri = "/control", + .method = HTTP_GET, + .handler = cmd_handler, + .user_ctx = NULL}; + + httpd_uri_t capture_uri = { + .uri = "/capture", + .method = HTTP_GET, + .handler = capture_handler, + .user_ctx = NULL}; + + httpd_uri_t stream_uri = { + .uri = "/stream", + .method = HTTP_GET, + .handler = stream_handler, + .user_ctx = NULL}; + + httpd_uri_t xclk_uri = { + .uri = "/xclk", + .method = HTTP_GET, + .handler = xclk_handler, + .user_ctx = NULL}; + + httpd_uri_t reg_uri = { + .uri = "/reg", + .method = HTTP_GET, + .handler = reg_handler, + .user_ctx = NULL}; + + httpd_uri_t greg_uri = { + .uri = "/greg", + .method = HTTP_GET, + .handler = greg_handler, + .user_ctx = NULL}; + + httpd_uri_t pll_uri = { + .uri = "/pll", + .method = HTTP_GET, + .handler = pll_handler, + .user_ctx = NULL}; + + httpd_uri_t win_uri = { + .uri = "/resolution", + .method = HTTP_GET, + .handler = win_handler, + .user_ctx = NULL}; + + httpd_uri_t mdns_uri = { + .uri = "/mdns", + .method = HTTP_GET, + .handler = mdns_handler, + .user_ctx = NULL}; + + httpd_uri_t monitor_uri = { + .uri = "/monitor", + .method = HTTP_GET, + .handler = monitor_handler, + .user_ctx = NULL}; + + ESP_LOGI(TAG, "Starting web server on port: '%d'", config.server_port); + if (httpd_start(&camera_httpd, &config) == ESP_OK) + { + httpd_register_uri_handler(camera_httpd, &index_uri); + httpd_register_uri_handler(camera_httpd, &cmd_uri); + httpd_register_uri_handler(camera_httpd, &status_uri); + httpd_register_uri_handler(camera_httpd, &capture_uri); + + httpd_register_uri_handler(camera_httpd, &xclk_uri); + httpd_register_uri_handler(camera_httpd, ®_uri); + httpd_register_uri_handler(camera_httpd, &greg_uri); + httpd_register_uri_handler(camera_httpd, &pll_uri); + httpd_register_uri_handler(camera_httpd, &win_uri); + + httpd_register_uri_handler(camera_httpd, &mdns_uri); + httpd_register_uri_handler(camera_httpd, &monitor_uri); + } + + config.server_port += 1; + config.ctrl_port += 1; + ESP_LOGI(TAG, "Starting stream server on port: '%d'", config.server_port); + if (httpd_start(&stream_httpd, &config) == ESP_OK) + { + httpd_register_uri_handler(stream_httpd, &stream_uri); + } +} diff --git a/components/modules/web/app_httpd.hpp b/components/modules/web/app_httpd.hpp new file mode 100644 index 0000000..28d08da --- /dev/null +++ b/components/modules/web/app_httpd.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +void register_httpd(const QueueHandle_t frame_i, const QueueHandle_t frame_o, const bool return_fb); diff --git a/components/modules/web/app_mdns.c b/components/modules/web/app_mdns.c new file mode 100644 index 0000000..ad9f6da --- /dev/null +++ b/components/modules/web/app_mdns.c @@ -0,0 +1,250 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2020 + * + * Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp_camera.h" +#include "mdns.h" +#include "who_camera.h" + +static const char *TAG = "camera mdns"; + +static const char * service_name = "_esp-cam"; +static const char * proto = "_tcp"; +static mdns_result_t * found_cams = NULL; +static SemaphoreHandle_t query_lock = NULL; +static char iname[64]; +static char hname[64]; +static char framesize[4]; +static char pixformat[4]; +static const char * model = NULL; + +static void mdns_query_for_cams() +{ + mdns_result_t * new_cams = NULL; + esp_err_t err = mdns_query_ptr(service_name, proto, 5000, 4, &new_cams); + if(err){ + ESP_LOGE(TAG, "MDNS Query Failed: %s", esp_err_to_name(err)); + return; + } + xSemaphoreTake(query_lock, portMAX_DELAY); + if (found_cams != NULL) { + mdns_query_results_free(found_cams); + } + found_cams = new_cams; + xSemaphoreGive(query_lock); +} + +static void mdns_task(void * arg) +{ + for (;;) { + mdns_query_for_cams(); + //delay 55 seconds + vTaskDelay((55 * 1000) / portTICK_PERIOD_MS); + } + vTaskDelete(NULL); +} + +/* +* Public Functions +*/ + +const char * app_mdns_query(size_t * out_len) +{ + //build JSON + static char json_response[2048]; + char *p = json_response; + *p++ = '['; + + //add own data first + tcpip_adapter_ip_info_t ip; + if (strlen(CONFIG_ESP_WIFI_SSID)) { + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + } else { + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + } + *p++ = '{'; + p += sprintf(p, "\"instance\":\"%s\",", iname); + p += sprintf(p, "\"host\":\"%s.local\",", hname); + p += sprintf(p, "\"port\":80,"); + p += sprintf(p, "\"txt\":{"); + p += sprintf(p, "\"pixformat\":\"%s\",", pixformat); + p += sprintf(p, "\"framesize\":\"%s\",", framesize); + p += sprintf(p, "\"stream_port\":\"81\","); + p += sprintf(p, "\"board\":\"%s\",", CAMERA_MODULE_NAME); + p += sprintf(p, "\"model\":\"%s\"", model); + *p++ = '}'; + *p++ = ','; + p += sprintf(p, "\"ip\":\"" IPSTR "\",", IP2STR(&(ip.ip))); + p += sprintf(p, "\"id\":\"" IPSTR ":80\",", IP2STR(&(ip.ip))); + p += sprintf(p, "\"service\":\"%s\",", service_name); + p += sprintf(p, "\"proto\":\"%s\"", proto); + *p++ = '}'; + + xSemaphoreTake(query_lock, portMAX_DELAY); + if (found_cams) { + *p++ = ','; + } + mdns_result_t * r = found_cams; + mdns_ip_addr_t * a = NULL; + int t; + while(r){ + *p++ = '{'; + if(r->instance_name){ + p += sprintf(p, "\"instance\":\"%s\",", r->instance_name); + } + if(r->hostname){ + p += sprintf(p, "\"host\":\"%s.local\",", r->hostname); + p += sprintf(p, "\"port\":%u,", r->port); + } + if(r->txt_count){ + p += sprintf(p, "\"txt\":{"); + for(t=0; ttxt_count; t++){ + if (t > 0) { + *p++ = ','; + } + p += sprintf(p, "\"%s\":\"%s\"", r->txt[t].key, r->txt[t].value?r->txt[t].value:"NULL"); + } + *p++ = '}'; + *p++ = ','; + } + a = r->addr; + while(a){ + if(a->addr.type != IPADDR_TYPE_V6){ + p += sprintf(p, "\"ip\":\"" IPSTR "\",", IP2STR(&(a->addr.u_addr.ip4))); + p += sprintf(p, "\"id\":\"" IPSTR ":%u\",", IP2STR(&(a->addr.u_addr.ip4)), r->port); + break; + } + a = a->next; + } + p += sprintf(p, "\"service\":\"%s\",", service_name); + p += sprintf(p, "\"proto\":\"%s\"", proto); + *p++ = '}'; + r = r->next; + if (r) { + *p++ = ','; + } + } + xSemaphoreGive(query_lock); + *p++ = ']'; + *out_len = (uint32_t)p - (uint32_t)json_response; + *p++ = '\0'; + ESP_LOGI(TAG, "JSON: %uB", *out_len); + return (const char *)json_response; +} + +void app_mdns_update_framesize(int size) +{ + snprintf(framesize, 4, "%d", size); + if(mdns_service_txt_item_set(service_name, proto, "framesize", (char*)framesize)){ + ESP_LOGE(TAG, "mdns_service_txt_item_set() framesize Failed"); + } +} + +void app_mdns_main() +{ + uint8_t mac[6]; + + query_lock = xSemaphoreCreateBinary(); + if (query_lock == NULL) { + ESP_LOGE(TAG, "xSemaphoreCreateMutex() Failed"); + return; + } + xSemaphoreGive(query_lock); + + sensor_t * s = esp_camera_sensor_get(); + if(s == NULL){ + return; + } + switch(s->id.PID){ + case OV2640_PID: model = "OV2640"; break; + case OV3660_PID: model = "OV3660"; break; + case OV5640_PID: model = "OV5640"; break; + case OV7725_PID: model = "OV7725"; break; + default: model = "UNKNOWN"; break; + } + + if (strlen(CONFIG_ESP_HOST_NAME) > 0) { + snprintf(iname, 64, "%s", CONFIG_ESP_HOST_NAME); + } else { + if (esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) { + ESP_LOGE(TAG, "esp_read_mac() Failed"); + return; + } + snprintf(iname, 64, "%s-%s-%02X%02X%02X", CAMERA_MODULE_NAME, model, mac[3], mac[4], mac[5]); + } + + snprintf(framesize, 4, "%d", s->status.framesize); + snprintf(pixformat, 4, "%d", s->pixformat); + + char * src = iname, * dst = hname, c; + while (*src) { + c = *src++; + if (c >= 'A' && c <= 'Z') { + c -= 'A' - 'a'; + } + *dst++ = c; + } + *dst++ = '\0'; + + if(mdns_init() != ESP_OK){ + ESP_LOGE(TAG, "mdns_init() Failed"); + return; + } + + if(mdns_hostname_set(hname) != ESP_OK){ + ESP_LOGE(TAG, "mdns_hostname_set(\"%s\") Failed", hname); + return; + } + + if(mdns_instance_name_set(iname) != ESP_OK){ + ESP_LOGE(TAG, "mdns_instance_name_set(\"%s\") Failed", iname); + return; + } + + if(mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0) != ESP_OK){ + ESP_LOGE(TAG, "mdns_service_add() HTTP Failed"); + return; + } + + + mdns_txt_item_t camera_txt_data[] = { + {(char*)"board" ,(char*)CAMERA_MODULE_NAME}, + {(char*)"model" ,(char*)model}, + {(char*)"stream_port" ,(char*)"81"}, + {(char*)"framesize" ,(char*)framesize}, + {(char*)"pixformat" ,(char*)pixformat} + }; + + if(mdns_service_add(NULL, service_name, proto, 80, camera_txt_data, 5)) { + ESP_LOGE(TAG, "mdns_service_add() ESP-CAM Failed"); + return; + } + + xTaskCreate(mdns_task, "mdns-cam", 2048, NULL, 2, NULL); +} \ No newline at end of file diff --git a/components/modules/web/app_mdns.h b/components/modules/web/app_mdns.h new file mode 100644 index 0000000..002a448 --- /dev/null +++ b/components/modules/web/app_mdns.h @@ -0,0 +1,16 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + + void app_mdns_main(); + void app_mdns_update_framesize(int size); + const char *app_mdns_query(size_t *out_len); + +#ifdef __cplusplus +} +#endif diff --git a/components/modules/web/app_wifi.c b/components/modules/web/app_wifi.c new file mode 100644 index 0000000..9a6a5bb --- /dev/null +++ b/components/modules/web/app_wifi.c @@ -0,0 +1,196 @@ +/* ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include +#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 "sdkconfig.h" + +#include "lwip/err.h" +#include "lwip/sys.h" + +#include "mdns.h" + +/* The examples use WiFi configuration that you can set via 'make menuconfig'. + + If you'd rather not, just change the below entries to strings with + the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid" +*/ +#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID +#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD +#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY +#define EXAMPLE_ESP_WIFI_AP_SSID CONFIG_ESP_WIFI_AP_SSID +#define EXAMPLE_ESP_WIFI_AP_PASS CONFIG_ESP_WIFI_AP_PASSWORD +#define EXAMPLE_MAX_STA_CONN CONFIG_MAX_STA_CONN +#define EXAMPLE_IP_ADDR CONFIG_SERVER_IP +#define EXAMPLE_ESP_WIFI_AP_CHANNEL CONFIG_ESP_WIFI_AP_CHANNEL + +static const char *TAG = "camera wifi"; + +static int s_retry_num = 0; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) + { + case SYSTEM_EVENT_AP_STACONNECTED: + ESP_LOGI(TAG, "station:" MACSTR " join, AID=%d", + MAC2STR(event->event_info.sta_connected.mac), + event->event_info.sta_connected.aid); + break; + case SYSTEM_EVENT_AP_STADISCONNECTED: + ESP_LOGI(TAG, "station:" MACSTR "leave, AID=%d", + MAC2STR(event->event_info.sta_disconnected.mac), + event->event_info.sta_disconnected.aid); + break; + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + ESP_LOGI(TAG, "got ip:%s", + ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); + s_retry_num = 0; + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + { + if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) + { + esp_wifi_connect(); + s_retry_num++; + ESP_LOGI(TAG, "retry to connect to the AP"); + } + ESP_LOGI(TAG, "connect to the AP fail"); + break; + } + default: + break; + } + mdns_handle_system_event(ctx, event); + return ESP_OK; +} + +void wifi_init_softap() +{ + if (strcmp(EXAMPLE_IP_ADDR, "192.168.4.1")) + { + int a, b, c, d; + sscanf(EXAMPLE_IP_ADDR, "%d.%d.%d.%d", &a, &b, &c, &d); + tcpip_adapter_ip_info_t ip_info; + IP4_ADDR(&ip_info.ip, a, b, c, d); + IP4_ADDR(&ip_info.gw, a, b, c, d); + IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); + ESP_ERROR_CHECK(tcpip_adapter_dhcps_stop(WIFI_IF_AP)); + ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(WIFI_IF_AP, &ip_info)); + ESP_ERROR_CHECK(tcpip_adapter_dhcps_start(WIFI_IF_AP)); + } + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config_t)); + snprintf((char *)wifi_config.ap.ssid, 32, "%s", EXAMPLE_ESP_WIFI_AP_SSID); + wifi_config.ap.ssid_len = strlen((char *)wifi_config.ap.ssid); + snprintf((char *)wifi_config.ap.password, 64, "%s", EXAMPLE_ESP_WIFI_AP_PASS); + wifi_config.ap.max_connection = EXAMPLE_MAX_STA_CONN; + wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK; + if (strlen(EXAMPLE_ESP_WIFI_AP_PASS) == 0) + { + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } + if (strlen(EXAMPLE_ESP_WIFI_AP_CHANNEL)) + { + int channel; + sscanf(EXAMPLE_ESP_WIFI_AP_CHANNEL, "%d", &channel); + wifi_config.ap.channel = channel; + } + + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config)); + + ESP_LOGI(TAG, "wifi_init_softap finished.SSID:%s password:%s", + EXAMPLE_ESP_WIFI_AP_SSID, EXAMPLE_ESP_WIFI_AP_PASS); +} + +void wifi_init_sta() +{ + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config_t)); + snprintf((char *)wifi_config.sta.ssid, 32, "%s", EXAMPLE_ESP_WIFI_SSID); + snprintf((char *)wifi_config.sta.password, 64, "%s", EXAMPLE_ESP_WIFI_PASS); + + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); + + ESP_LOGI(TAG, "wifi_init_sta finished."); + ESP_LOGI(TAG, "connect to ap SSID:%s password:%s", + EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS); +} + +void app_wifi_main() +{ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + wifi_mode_t mode = WIFI_MODE_NULL; + + if (strlen(EXAMPLE_ESP_WIFI_AP_SSID) && strlen(EXAMPLE_ESP_WIFI_SSID)) + { + mode = WIFI_MODE_APSTA; + } + else if (strlen(EXAMPLE_ESP_WIFI_AP_SSID)) + { + mode = WIFI_MODE_AP; + } + else if (strlen(EXAMPLE_ESP_WIFI_SSID)) + { + mode = WIFI_MODE_STA; + } + + 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); + + if (mode == WIFI_MODE_NULL) + { + ESP_LOGW(TAG, "Neither AP or STA have been configured. WiFi will be off."); + return; + } + + tcpip_adapter_init(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_mode(mode)); + + if (mode & WIFI_MODE_AP) + { + wifi_init_softap(); + } + + if (mode & WIFI_MODE_STA) + { + wifi_init_sta(); + } + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); +} diff --git a/components/modules/web/app_wifi.h b/components/modules/web/app_wifi.h new file mode 100644 index 0000000..dc164e9 --- /dev/null +++ b/components/modules/web/app_wifi.h @@ -0,0 +1,37 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2017 + * + * Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +#ifndef _APP_WIFI_H_ +#define _APP_WIFI_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void app_wifi_main(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/modules/web/www/compress_pages.sh b/components/modules/web/www/compress_pages.sh new file mode 100755 index 0000000..f7c13fb --- /dev/null +++ b/components/modules/web/www/compress_pages.sh @@ -0,0 +1,7 @@ +#!/bin/bash +for file in `ls *.html`; do + echo "Compressing: $file" + cp "$file" "copy_$file" && \ + gzip -f "$file" && \ + mv "copy_$file" "$file" +done diff --git a/components/modules/web/www/index_ov2640.html b/components/modules/web/www/index_ov2640.html new file mode 100644 index 0000000..cac38f9 --- /dev/null +++ b/components/modules/web/www/index_ov2640.html @@ -0,0 +1,1152 @@ + + + + + + ESP32 OV2460 + + + +
+ +
+ +
+ +
+
+
+ + + diff --git a/components/modules/web/www/index_ov2640.html.gz b/components/modules/web/www/index_ov2640.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..b0e1ddb0137af02160473b6e5403767ce29d32bf GIT binary patch literal 6787 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 + + + + + ESP32 OV3660 + + + +
+ +
+ +
+ +
+
+
+ + + diff --git a/components/modules/web/www/index_ov3660.html.gz b/components/modules/web/www/index_ov3660.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..8274a211476a8aae52e797293247e0a53610d97f GIT binary patch literal 8887 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 F0RX(*5(WSO literal 0 HcmV?d00001 diff --git a/components/modules/web/www/index_ov5640.html b/components/modules/web/www/index_ov5640.html new file mode 100644 index 0000000..a6c01f9 --- /dev/null +++ b/components/modules/web/www/index_ov5640.html @@ -0,0 +1,1391 @@ + + + + + + ESP32 OV5640 + + + +
+ +
+ +
+ +
+
+
+ + + diff --git a/components/modules/web/www/index_ov5640.html.gz b/components/modules/web/www/index_ov5640.html.gz new file mode 100644 index 0000000000000000000000000000000000000000..b5f4f48ebe9bf9eb6076e8c0d0391fd358722c25 GIT binary patch literal 9124 zcmV;VBU{`biwFqWY-(Nr18Ht#Wq4n2b~QFMFfM3xZEOJTJ!^N{wvylTD;V`|B%fHe zsFxqHl}#Kc>A6jB8mDP{x;-f+K@uB^)Fmm)ZjOJ){-FJ1I|G0uNP#3sO0wHs<(!xg z%wXOa3_$Yi@z@#pw+o`p{JAxFbjJQ@@H?9kW6Tce>74k6Hkui(NBr~J(w{VsYsuTT zF(>D>Ym=-Nj_cR7kz@PBhUP1C?9a}}G5RufouxfK)xOb>@!xu*kz+Y7+`oK{|JLu*-aYeAa;oY5#ZBz~ z-J{s)ter+Zhd<7S_sGaM9s6Kzn6?Dd*z^{b0Wz3aBoog!b7C*|bFd+j=Gb&ut5cN2 za&G6M&7#Y8Y$6dLcC>Ph1vbFH>rLjS&5Y_)>vUU-n;cbk`uK0X(ZU#yO?!H(9ZOL- z?wGhu*BF~i?^Nr`+RP1i3bgnR5ZlzHl<+35Ii2~S(zP~8p>Y?=(`TmKMB9Z*rLo$;5Gqv}Mr3az^`i-MwtrQev&Zk*cLU~>9jQfq=YW>Ih%^6{4 zYAk)Hx<{Xbr)gk6U0HH?ybau|+Ou5$c| zMp1xWIjd3ZVE<$`#5hTfBKT8}BoVUQXd*$J9#Pitrg^snYc>h0(M5tdi-z1NgTFh4 zeB+zp()S(Pt29KV`Pm;!&o?KxP43d*XA1*T$RY7pgk+~1ws8${XF9cHwl`Y39_DQe z#{|UgddG1eRP3lZ8cOC|E?I;RF1u-I;AB^ABJO|}q3Vh5HnBvuy5+!<2BE{|2|xf`V{8oatk z|Dh~Ldb}UI=_v9BGl zgA#d)pz%yXbg8|tR7L7bach&N{<*;-m;_EHdDF$!TXyL!C z9;ti%iZzgP>Jq<6`V59uW3eCxv>K7L*{V`HEstqRbJSc@3=^e7JfBXS(NZ>otC`d~ zzSBTto@G+QUfMQlwCTIRrlcb^!IkpPm|)ZDGzkZk1d1?*Xr2R11*odY#h)$bvYa94 zOosuEb>~ipMx(Xg+V8-BlH5JpxVU&zEH4p!@=GXbVJue{>Yfy@rRWDaBTR}(7dJ$S zwG>tH-1)P~hS&e|)-H(RZ*A#?<>KA9ZM4$0-sJfVP-sKO>B7V-HRlo9l#s#1w5+D( ztnxFFY)f9V(|nUJOYIUO*I$C#zqi9>x<qyCzSSFY`tMgW-Kbn=8R4p9O zWXm@$u?)=MOU=e}Cnh4e3MW)_52uoHz;cK5SS>f}oOD0#8S+L2B6<+Cr!KiwU}m5H zJ!Q4h@)};C{%Q*HhA*Q$?GiJ3FM<5)>Q) z2`NPJY*0p5(8PlTXK1*RCbSuwYtR@{qskaMH%ZbdH(XP^6&a@?1TYGY>#Wd5+9)P_ zY;hYYTG2Vh|DwbyT7HT2eTRb=`}ZSd0%5oP^W6=cW2CeX!Db-RU7p~ySs zM6Mq}zm$ho_xMKd^}7{#=%aLjkj8PpTNPIiae+XN+8~eiaTU>aYXhqC6GYW2h6+X7 zE-BhhZSW(C7A*sKsAL{(h*A_+8|}8$t|D5!Hb8M8rlU#@0NzAt0&;GS`p4xlfhS#k z0!+sss7h`GUW9o1-PTRFip_vmyhTKS~PgYn%S&kwy77KPP&HY7uHGu2oahe-J3>2QvL?|@C<*+s)dC$5`jWK5FS_3N|o>> zki+#7gs|?G^pZzyCB4iHcTp;Qg^-|?;PBpDB_if;;45QdJEm8Bw$I^egIA>aJswq2 z$BU)!R8UAj3sXmnYvXuJ+Bn{#HVpFFBG<$!td0P%Hn_Z5I37Ma^&Os9a*|#JorDB& zAL((e^tPmxJX))0WonrAh?o{W#!*x;0&H#YCOWC2m{tY7i1ft$r3bU}E$K$rw>hdT z$jCIT&tx)zX;AT@B?b=T3p>{?>3QM$WddFKB4%<U zh_MFx+KHmD*ZWZv<@df71=jKz$I&uQMFDGrcZR)$L1tG93p>6y_u_otx9Wh9xic<4 z-QjR7B))^A!ihnEVapi-n@W{W2CkO2eOo0HxUih%xI6|(1O7Fcn4|KE1SA1(ow*|X zmIq!kDlwBAffg?>b~^AvpZ63QklXtX3P8kXYCL6>#UR6o9!$UQYkn6|6rbJU(sMYy z>$EpJ!9(QR)la*OY9JbYkLwHt0(NyrYTutHwI6^hO-n?)DMq|E7rW8W^xi_<^j<@q z#y159QYe|hazIwgsuJY%n<`N*AR`{+`q8RLZ?ns~-8^cmIE`zpK2PUHVP$YQ(8aqK zyG$2?e(%SRLay&S)>*_Z9_$dPHu#R%o_685{6&~M(HiafepJSozHfca=BDd9Zef*i zIG9#6-_fI;s4M98el!;HeBVmDo>=Chu*xV@ZSbl28vgD?U989ZQ5NU+zEw3`6c2|0 zQyaW|znk0-J5$t9C4Eq)?pqtH653cH;Xmz28!L5fY}j8`4?#cl>C>T6d^idKanl$a zX)lf43`k+WyKte1$@|vR#2As!W8#-tTNYqzgV%7Py`m?(O8Lb8ccHZyzYjujkYGDg zyVT=MF>tQF5A1xm3XF)@nGOZMH>gI%ryBX2f%A05pRS{uT8Q8DeABXEDolLs3J(<@ zrxUOQ57GKAG3K?w6}~eWaN${TWK@jAb{!xpR1<$cWlal?glmIK+E_z7%kNJ4IY0VD zFFv9hf1^6+mA%zqa7Nz_%bU@M*;oAODIC&r}@3V$F|(*qhcFpER_j3qN>wdZ%mF8RwDq>p14%1@FHPscIl z8U5pCbJoSDMYVvnv%^_8@6oW_M}2|Q(og!LlmI|-EKFXR_j=Wb;SpNH^S2#q^ZbLH{eOw6@D|BJT#8@x>R|VTJ2pw zolT8 zXbf*Q=_DzpTgpy(*ct3*T%y^Os8HTJJ`>vM^3-V)>QR3YL~4`GBxVaC!&Oeq-hcO9 zv0O~-<$>g4nYGZ{1>8WYOPOv2UK_lm$C@CqLdL5+?WBN0u{0tfsT)OH83j^-J$0rk z`vh&0mkKXjQd(^swl;Y0677|Ft3OtB#<~ojFS{(r}r1@}EI+{gj${2de zCg+ObOfMGBcIQ6cZTXae!@_L!7G{WZRG8Lif&1vaU3yPIM!`3*iQe-toQLXzSmR!K zjReq;58!+7D*2IstehiNcN(cHR?G|9l}Gi?LmUei`07NWw!M-f6O%{=%Z-L@E8l76 z9}uv<+EjcMUSLi(5eRvGaI*j;tn@U119KIIk+`E5h{WQIAjDU4w7d88zbg+O6`4L+nL(Wr(&+gDNxJRx{`A9y;snGS?A~W-ucs$f+5N$&woX`IP~t!tJuEe?J|pVIZn zZsS$hdBM_*po{*)GA+)Ls&z>?>OLIP@*Ur>N?R6#2E+P@2ig_zxl`i``Y#{2j=LLD zMg^9BS!bzI=<081k$fYeYw6?F(xHoiW9XtsyEn0rg7P-jJK5wGm`y}5##1FD+mzo_ zHnNeS{Ejz@Ow8sPEX?89!A>hSLFXG^s13P#NcEYaY1?Jh#9?cLm+WA-OX|Gy&9A2i zTX z0FmC1Kx)5LcAKqBL=VK#k{UwnJ)5k3`~?N=p6vanazW@)LE!Gb&GOU^c+%B)YFFT? zy(6AF0Z(l;o;nqH>gTp&|p2-4?$-pj20&Kt(DFJ+Y$DSFR{9;qB<%fDt(Lg(F^$gQJwEz zdA>VE`0j0y?{2{NCw0EN<@xRw;k&yx$M4W5CXz{JDS#h9z5wN#|^(v(BM8a9Y0Dn^69SUg}Y2BWR zdoe+Bd76c)Fz#>b*KNl;#i@ofw^j^+p3T>1H#_&~0Bh#Z@f?%}w!D%QW;Th$4`z-6}WQ2uM z&+y_4b^81pUlpDOPJ7qL!BIinTYtsCAfYIo>PN?Ou^MsGv@ZEe^(XTFj`o zwmJyNJ5&H2WtEnnW_EUvpP;m292Hb)v8B(LO4a9=oXD-Iv`YohRaR;FSx09FyLVAq zF^&qVwAhkeOr`4cNlxU}RNA8g=qanTe5IhXgJKX$E5=bll@?olj;U0Aor4p(HI??M z0Q$-*Ex*62vxB|cD6JSr1yx$CbQ@Et`pOL_a%(C*q5?QlR%!XI>zy6c^H5qbjtZ)@ zSOq7hQuV!4oXD-I^q30ZSXrgzckXv~uyY@!72~L&N{iKIV=7hO?!<}Qno3Wo08W%u zT7JtwX9t^KP+BdKqk<|ew!qj)+4`cO(zFG}2T;E=R>Kk>{CV-y%k;v2Y85Zm*F!Jk ziSk0iLCfqVk=h#b*_OtD3s_pG+ma_OIhMw$+)nU7#8lcuIAZD{1UfP@rlS&} z?(*XB#hVL_E@?Ve!H5^{^e+|jDCt7fdvl>0@Hk|Vkk@N<%2WtLBDWOuDEf9JgUezv zC{!hl%b-Bm4~X2R3@}PwUTE+Csa@VI9B=88+^E9g;&G+l>vdEEOD}y0n6x$+FDaqj zkiicGG_4djIbfVay@lJW6&aLgl%0(Z2b9WQ$lj-qS89wU>uAMPm-fhL!9hXZ=P?NSh-F^Pqe5u0XPLV@jgy8VllC!P#I4jcdX`m#WQN zGC8k(6XN8L-(i!QHg;CF5DKe`q)4WXWD_0wa$j`kOJF(DAt@9e@mh-w>L(Zk%EY-PHFryc35N zh@BjE3*zDaQQ2iW3@ISu7|94Ml)cwa-{B!pNdV*ux-`($24(rmJ zrb;gqTVa}}8ASaa`EO(7j6pp(-k&)j{kvz@K36t0M_@G)nFf(0~=XFJoIicGC7X_d0$(&Kp6d&3LFC>Vza-w z6f7+3Il~GhumdoeC7Le$fA<7BfkXKJ?g@5UE~E7z%j2AAs}xe-T_9G(9PFw9oXO&6ye?}4;zSxak++zT~0&C?xtYmp)d}C7t^rOQA5Dh z?)4&UiAcxhlqiq`%+I5ta0KR{)q@FT{d6tD0R%4~XwMmqr&0LSv>_xrU!KK~(bPZ0 z#I9kYkhsLQ+ch1V$a{^&OWqMb8TP7%GBM;c;?Q0IPKU zM*Y%t9rxGzQ*89K{@bZWJxC`O)@KAf<`vxcgYb-OAk7n>B&~ecy+4t^2BA8fu z01_mb6xG4Mm&Cojq5^Z=7Z57z2hlvK&T^guKy?XyfQ}=ZXWWE(hjH$lD6;SI$Pkgr4=W#4v5sHx27kWQ|*Oe~FJ5@c*@(Pc2VbvydZ2?ej?PT&ZT3m6iCe*x(Y+QR=w{Rb5Fw-@3wY&+or z3{IX;KZQhFo+O8%JDlvpWag5E=a;wNjvMvxjYjo7j7yd|`Vxkv$nqei!R#L=*5-(t z>Z=bg-hF=Y@#6<=2{x>w>eJvF= z9d5^C7qm^SWpB-e)o@^A368btW+oEaTEMcF@U-qP*I^o)Dr#&hXl%M!jd9eKwHDyX z(OfP(R~pyWRHm&mYGSbqCPl=3`|;g(7;WS#3pJKZt|S;RD8?!uiLj|f6@(l^-~|IU z-^C(9;Jgu3hAkj~k`fu_HHQ&u89R3o>aU(^Pz$E&QEc;arNbxmN&@0vm6I4aF9a08 zax1|?3kXU$=$FV+FsM;MKm8@a5R@vEkOZHtO2?x{Oz^QN2_9Y=Q9{E%bd-ukjTruI zAsJ7+6r;uy|0GU29!1&k&(O%Mj#Yj#9fLIiV4kWz*Tu}57mBhl@FqzNL3Fr>lHGko zq4$=h7KIj;6@;G#2pVTQ3Cz zEeWgTX3#PP$--4j@1R{8BUpb{VV)t~KiIaj<5@n;nc@WA!a= zYG`1m`>p*JZ{WMPVU~$_aq;)Z58p+2ediUs)Tq;45r=;)a4HZeMGzxyctBCH=Fdc= zaM>m+4KR_03^dpdf#Ar=>!oG=LJSx5@l+w$>0UI(!+CprJPckG~U* z16πCt|Z1DDL5YqF+)Y#ti_%p9{aO=P!N6~YZ7xP=1uQ#9&q_X-^q?jpCDIjby4 z)_VdtgdUroF|_i#=IzG3{0$9)D*)=dv`#3XC3YUxfUHc!69Xnsam=iET%fEhUIvKx zDVQ@@sP>$)pq<9_C&Y`Beu4Ip7}tUY^uc(k&>NrIu^ zaZK>S7y*QsV=5rrl~!9@?|)9MzCrk=#66Hd8gq0ZK8H?VoW?LBV+p$PcX&uKP7Tw( zASR}Nd%|7XJs`pjgTivg`g+LWA#Iw1hIX-zCX@Qr@H1kQ35B9t4N)AlS2)ZMw=v;|iBk0!v8v2Calu45TIY$DdH zD;B{saI(#PNu~>v~;^E2W+;dLoNvbg2;##kLBJ*lzXbFyi6{0918i3lKxW z_O4MB@!e~|je!U`*gRa#}0Elwt*6wfgqsq{^EU8-~`$u9B7!k8tD`3XPT<8lHM z83*x}84<2NGd;#kfJvJRJAX?eUv>$V^@j&}dnf`K4(vcc5ED@$6=leb{e%IVj@Gp2 zU#V69w$Da*A!zOuA!uz?K9`t{txpC&h{Y7&xz<`+mlqe&LIx$W5f;@0@kkfYQpLsW zi$xTi7G%z!=!RJ;uL$5G+#sC`8EvK(aM1af+WG^Zx(T3&H6J#3N+wCZIC^El(_xY| z7NDu;=L3t|vg?AdJ>OYe(W{B5u%gAR1H2#1lSD@2_5UlZbZHDvJ)Z5-BAB!XI_LD< z5gRTn}?$Y8z?b+3Cd52k&&2qP>K^TjC^bl z{qxVGAq;jn0R-1uHTLeZ*kR&|6sbxCjoNZ-G5N^RiPn2*2NRYw!Je;+mgTH51W`O* zBQ}wg3U(1R2xu1_B8B$&SeZ!w0+nAZfrxpaa&`?f+|V{5+FhiFamvfo3DtOh5v7*V6@wAfVk|s22p{nP{Yy^x4GvOd_GM^clqZ?BPz( zLh!^^BuJ7ibs|bVm`UY8e5R?0C^DibGQvfM08!$eKdlSn%1uSxD@d-p?JPSTljiaB7ze z8nzUmLpm~{gGOv@3s04V6}FC?Cqlp`N&~HzEGMvphcTN-Ys3a2OR+YggE06sGIL2` zGixLGt<3f6`M5=+7+(-?>Rndw35j_;Etk85EuG?%^g ztyM8`GMGq~k2#NP!6<9`UO`Q&uC%&?uG*rExx8D@@`n iRcF7m!{D(wc7}@ugGXnFGk + + + + + + ESP-EYE Monitor + + +
+
+
+
+ + + + + + + + + +
+
+
+
+ + + + + + + + +
+
+
+ +
+
+
+

Console

+
+
+
+
+
+ + diff --git a/components/modules/web/www/monitor.html.gz b/components/modules/web/www/monitor.html.gz new file mode 100755 index 0000000000000000000000000000000000000000..ef4a8fccd98983c1db1f949e790969539a53a65e GIT binary patch literal 37438 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 B8?OKW literal 0 HcmV?d00001