Add the missing Save button to the web UI for OV5640 (#136)

* Add the missing Save button to the web UI for OV5640

* Add MDNS feature that allows the cameras to be found

* Add mDNS Camera Query
ov5640-save-button^2
Me No Dev 2020-03-27 10:47:07 +02:00 committed by GitHub
parent 7937d37e14
commit 67a57377cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 370 additions and 30 deletions

View File

@ -1,4 +1,4 @@
set(COMPONENT_SRCS "app_main.c" "app_wifi.c" "app_camera.c" "app_httpd.c") set(COMPONENT_SRCS "app_main.c" "app_wifi.c" "app_camera.c" "app_httpd.c" "app_mdns.c")
set(COMPONENT_ADD_INCLUDEDIRS "include") set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_REQUIRES set(COMPONENT_REQUIRES

View File

@ -20,6 +20,7 @@
#include "driver/ledc.h" #include "driver/ledc.h"
//#include "camera_index.h" //#include "camera_index.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include "app_mdns.h"
#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG) #if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
#include "esp32-hal-log.h" #include "esp32-hal-log.h"
@ -705,8 +706,12 @@ static esp_err_t cmd_handler(httpd_req_t *req)
int res = 0; int res = 0;
if (!strcmp(variable, "framesize")) { if (!strcmp(variable, "framesize")) {
if (s->pixformat == PIXFORMAT_JPEG) if (s->pixformat == PIXFORMAT_JPEG) {
res = s->set_framesize(s, (framesize_t)val); res = s->set_framesize(s, (framesize_t)val);
if (res == 0) {
app_mdns_update_framesize(val);
}
}
} }
else if (!strcmp(variable, "quality")) else if (!strcmp(variable, "quality"))
res = s->set_quality(s, val); res = s->set_quality(s, val);
@ -878,6 +883,15 @@ static esp_err_t status_handler(httpd_req_t *req)
return httpd_resp_send(req, json_response, strlen(json_response)); 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) static esp_err_t xclk_handler(httpd_req_t *req)
{ {
char *buf = NULL; char *buf = NULL;
@ -1137,6 +1151,12 @@ void app_httpd_main()
.handler = win_handler, .handler = win_handler,
.user_ctx = NULL}; .user_ctx = NULL};
httpd_uri_t mdns_uri = {
.uri = "/mdns",
.method = HTTP_GET,
.handler = mdns_handler,
.user_ctx = NULL};
ra_filter_init(&ra_filter, 20); ra_filter_init(&ra_filter, 20);
#if CONFIG_ESP_FACE_DETECT_ENABLED #if CONFIG_ESP_FACE_DETECT_ENABLED
@ -1179,6 +1199,8 @@ void app_httpd_main()
httpd_register_uri_handler(camera_httpd, &greg_uri); httpd_register_uri_handler(camera_httpd, &greg_uri);
httpd_register_uri_handler(camera_httpd, &pll_uri); httpd_register_uri_handler(camera_httpd, &pll_uri);
httpd_register_uri_handler(camera_httpd, &win_uri); httpd_register_uri_handler(camera_httpd, &win_uri);
httpd_register_uri_handler(camera_httpd, &mdns_uri);
} }
config.server_port += 1; config.server_port += 1;

View File

@ -23,10 +23,12 @@
#include "app_camera.h" #include "app_camera.h"
#include "app_wifi.h" #include "app_wifi.h"
#include "app_httpd.h" #include "app_httpd.h"
#include "app_mdns.h"
void app_main() void app_main()
{ {
app_wifi_main(); app_wifi_main();
app_camera_main(); app_camera_main();
app_httpd_main(); app_httpd_main();
app_mdns_main();
} }

View File

@ -0,0 +1,242 @@
/*
* ESPRESSIF MIT License
*
* Copyright (c) 2020 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
*
* 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 <string.h>
#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 "app_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\",", CAM_BOARD);
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; t<r->txt_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);
if (esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
ESP_LOGE(TAG, "esp_read_mac() Failed");
return;
}
sensor_t * s = esp_camera_sensor_get();
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;
}
snprintf(iname, 64, "%s-%s-%02X%02X%02X", CAM_BOARD, 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*)CAM_BOARD},
{(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);
}

View File

@ -33,6 +33,8 @@
#include "lwip/err.h" #include "lwip/err.h"
#include "lwip/sys.h" #include "lwip/sys.h"
#include "mdns.h"
/* The examples use WiFi configuration that you can set via 'make menuconfig'. /* 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 If you'd rather not, just change the below entries to strings with
@ -83,6 +85,7 @@ static esp_err_t event_handler(void *ctx, system_event_t *event)
default: default:
break; break;
} }
mdns_handle_system_event(ctx, event);
return ESP_OK; return ESP_OK;
} }
@ -169,4 +172,5 @@ void app_wifi_main()
wifi_init_sta(); wifi_init_sta();
} }
ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
} }

View File

@ -25,6 +25,7 @@
#define _APP_CAMERA_H_ #define _APP_CAMERA_H_
#if CONFIG_CAMERA_MODEL_WROVER_KIT #if CONFIG_CAMERA_MODEL_WROVER_KIT
#define CAM_BOARD "WROVER-KIT"
#define PWDN_GPIO_NUM -1 #define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1 #define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21 #define XCLK_GPIO_NUM 21
@ -44,6 +45,7 @@
#define PCLK_GPIO_NUM 22 #define PCLK_GPIO_NUM 22
#elif CONFIG_CAMERA_MODEL_ESP32_CAM_BOARD #elif CONFIG_CAMERA_MODEL_ESP32_CAM_BOARD
#define CAM_BOARD "ESP-DEVCAM"
#define PWDN_GPIO_NUM 32 #define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM 33 #define RESET_GPIO_NUM 33
#define XCLK_GPIO_NUM 4 #define XCLK_GPIO_NUM 4
@ -63,6 +65,7 @@
#define PCLK_GPIO_NUM 25 #define PCLK_GPIO_NUM 25
#elif CONFIG_CAMERA_MODEL_ESP_EYE #elif CONFIG_CAMERA_MODEL_ESP_EYE
#define CAM_BOARD "ESP-EYE"
#define PWDN_GPIO_NUM -1 #define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1 #define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 4 #define XCLK_GPIO_NUM 4
@ -82,6 +85,7 @@
#define PCLK_GPIO_NUM 25 #define PCLK_GPIO_NUM 25
#elif CONFIG_CAMERA_MODEL_M5STACK_PSRAM #elif CONFIG_CAMERA_MODEL_M5STACK_PSRAM
#define CAM_BOARD "M5CAM"
#define PWDN_GPIO_NUM -1 #define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15 #define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27 #define XCLK_GPIO_NUM 27
@ -101,6 +105,7 @@
#define PCLK_GPIO_NUM 21 #define PCLK_GPIO_NUM 21
#elif CONFIG_CAMERA_MODEL_M5STACK_WIDE #elif CONFIG_CAMERA_MODEL_M5STACK_WIDE
#define CAM_BOARD "M5CAMW"
#define PWDN_GPIO_NUM -1 #define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15 #define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27 #define XCLK_GPIO_NUM 27
@ -120,6 +125,7 @@
#define PCLK_GPIO_NUM 21 #define PCLK_GPIO_NUM 21
#elif CONFIG_CAMERA_MODEL_AI_THINKER #elif CONFIG_CAMERA_MODEL_AI_THINKER
#define CAM_BOARD "AI-THINKER"
#define PWDN_GPIO_NUM 32 #define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1 #define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0 #define XCLK_GPIO_NUM 0
@ -140,6 +146,7 @@
#elif CONFIG_CAMERA_MODEL_CUSTOM #elif CONFIG_CAMERA_MODEL_CUSTOM
#define CAM_BOARD "CUSTOM"
#define PWDN_GPIO_NUM CONFIG_CAMERA_PIN_PWDN #define PWDN_GPIO_NUM CONFIG_CAMERA_PIN_PWDN
#define RESET_GPIO_NUM CONFIG_CAMERA_PIN_RESET #define RESET_GPIO_NUM CONFIG_CAMERA_PIN_RESET
#define XCLK_GPIO_NUM CONFIG_CAMERA_PIN_XCLK #define XCLK_GPIO_NUM CONFIG_CAMERA_PIN_XCLK

View File

@ -0,0 +1,31 @@
// Copyright 2015-2020 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.
#ifndef _CAMERA_MDNS_H_
#define _CAMERA_MDNS_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
void app_mdns_main();
void app_mdns_update_framesize(int size);
const char * app_mdns_query(size_t * out_len);
#ifdef __cplusplus
}
#endif
#endif /* _CAMERA_MDNS_H_ */

View File

@ -135,7 +135,7 @@
padding: 0 5px padding: 0 5px
} }
button { button, .button {
display: block; display: block;
margin: 5px; margin: 5px;
padding: 0 12px; padding: 0 12px;
@ -335,6 +335,17 @@
display: none display: none
} }
.save {
position: absolute;
right: 25px;
top: 0px;
height: 16px;
line-height: 16px;
padding: 0 4px;
text-decoration: none;
cursor: pointer
}
input[type=text] { input[type=text] {
border: 1px solid #363636; border: 1px solid #363636;
font-size: 14px; font-size: 14px;
@ -895,8 +906,9 @@
</div> </div>
<figure> <figure>
<div id="stream-container" class="image-container hidden"> <div id="stream-container" class="image-container hidden">
<a id="save-still" href="#" class="button save" download="capture.jpg">Save</a>
<div class="close" id="close-stream">×</div> <div class="close" id="close-stream">×</div>
<img id="stream" src=""> <img id="stream" src="" crossorigin>
</div> </div>
</figure> </figure>
</div> </div>
@ -1124,6 +1136,26 @@ document.addEventListener('DOMContentLoaded', function (event) {
}); });
} }
const saveButton = document.getElementById('save-still');
saveButton.onclick = () => {
var canvas = document.createElement("canvas");
canvas.width = view.width;
canvas.height = view.height;
document.body.appendChild(canvas);
var context = canvas.getContext('2d');
context.drawImage(view,0,0);
try {
var dataURL = canvas.toDataURL('image/jpeg');
saveButton.href = dataURL;
var d = new Date();
saveButton.download = d.getFullYear() + ("0"+(d.getMonth()+1)).slice(-2) + ("0" + d.getDate()).slice(-2) + ("0" + d.getHours()).slice(-2) + ("0" + d.getMinutes()).slice(-2) + ("0" + d.getSeconds()).slice(-2) + ".jpg";
} catch (e) {
console.error(e);
}
canvas.parentNode.removeChild(canvas);
}
const hide = el => { const hide = el => {
el.classList.add('hidden') el.classList.add('hidden')
} }