Feature: Control a LED Illuminator or Flash from Camera_Web_Server Web Page (#104)
* Feature: Control a LED Illuminator or Flash from Camera_Web_Server Add a "LED Illuminator" menu to menuconfig Through menuconfig you can select a LEDC pin, LEDC Timer, LEDC Channel, and LEDC Speed Mode The defaults work for the AIThinker ESPCAM-32 board There is an option to limit LED intensity while streaming to prevent overheating LED intensity is not limited when taking a still image photo The LED intensity controls are hidden on the web page if the LED feature is not enabled LED intensity can be adjusted while streaming and the LED will respond right away Tested on an ESPCAM-32 board with an OV2460 sensor * app_httpd.c: move isStreaming into macro * app_httpd.c: Change commit re: 150ms LED delaypull/131/head
parent
65d4587d76
commit
1169211177
|
@ -38,6 +38,59 @@ config ESP_MAXIMUM_RETRY
|
||||||
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
|
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
|
menu "LED Illuminator"
|
||||||
|
config LED_ILLUMINATOR_ENABLED
|
||||||
|
bool "LED Illuminator Enabled"
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Enable an LED Flash or IR Illuminator
|
||||||
|
|
||||||
|
config LED_LEDC_PIN
|
||||||
|
depends on LED_ILLUMINATOR_ENABLED
|
||||||
|
int "LED Illuminator GPIO Pin"
|
||||||
|
range 0 33
|
||||||
|
default 4
|
||||||
|
help
|
||||||
|
Set a pin to illuminate an onboard LED or IR Illuminator when streaming or taking snapshots.
|
||||||
|
|
||||||
|
config LED_MAX_INTENSITY
|
||||||
|
depends on LED_ILLUMINATOR_ENABLED
|
||||||
|
int "LED Maximum Intensity (0-255)"
|
||||||
|
range 0 255
|
||||||
|
default 255
|
||||||
|
help
|
||||||
|
Limit the maximum intensity of the LED while streaming to prevent overheating (0-255).
|
||||||
|
|
||||||
|
choice LED_LEDC_SPEED_MODE
|
||||||
|
depends on LED_ILLUMINATOR_ENABLED
|
||||||
|
bool "Select LEDC Timer Speed Mode"
|
||||||
|
default LED_LEDC_LOW_SPEED_MODE
|
||||||
|
help
|
||||||
|
Select a speed mode for the LEDC channel
|
||||||
|
|
||||||
|
config LED_LEDC_LOW_SPEED_MODE
|
||||||
|
bool "LOW_SPEED_MODE"
|
||||||
|
config LED_LEDC_HIGH_SPEED_MODE
|
||||||
|
bool "HIGH_SPEED_MODE"
|
||||||
|
endchoice
|
||||||
|
|
||||||
|
config LED_LEDC_TIMER
|
||||||
|
depends on LED_ILLUMINATOR_ENABLED
|
||||||
|
int "LEDC Timer"
|
||||||
|
range 0 3
|
||||||
|
default 1
|
||||||
|
help
|
||||||
|
Select the LEDC Timer (0-3)
|
||||||
|
|
||||||
|
config LED_LEDC_CHANNEL
|
||||||
|
depends on LED_ILLUMINATOR_ENABLED
|
||||||
|
int "LEDC Channel"
|
||||||
|
range 0 7
|
||||||
|
default 1
|
||||||
|
help
|
||||||
|
Select the LEDC Channel (0-7)
|
||||||
|
endmenu
|
||||||
|
|
||||||
menu "Camera Pins"
|
menu "Camera Pins"
|
||||||
choice CAMERA_MODEL
|
choice CAMERA_MODEL
|
||||||
bool "Select Camera Pinout"
|
bool "Select Camera Pinout"
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "driver/ledc.h"
|
||||||
#include "esp_camera.h"
|
#include "esp_camera.h"
|
||||||
#include "app_camera.h"
|
#include "app_camera.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
@ -44,6 +45,36 @@ void app_camera_main ()
|
||||||
gpio_config(&conf);
|
gpio_config(&conf);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_LED_ILLUMINATOR_ENABLED
|
||||||
|
gpio_set_direction(CONFIG_LED_LEDC_PIN,GPIO_MODE_OUTPUT);
|
||||||
|
ledc_timer_config_t ledc_timer = {
|
||||||
|
.duty_resolution = LEDC_TIMER_8_BIT, // resolution of PWM duty
|
||||||
|
.freq_hz = 1000, // frequency of PWM signal
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE, // timer mode
|
||||||
|
.timer_num = CONFIG_LED_LEDC_TIMER // timer index
|
||||||
|
};
|
||||||
|
ledc_channel_config_t ledc_channel = {
|
||||||
|
.channel = CONFIG_LED_LEDC_CHANNEL,
|
||||||
|
.duty = 0,
|
||||||
|
.gpio_num = CONFIG_LED_LEDC_PIN,
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
|
.hpoint = 0,
|
||||||
|
.timer_sel = CONFIG_LED_LEDC_TIMER
|
||||||
|
};
|
||||||
|
#ifdef CONFIG_LED_LEDC_HIGH_SPEED_MODE
|
||||||
|
ledc_timer.speed_mode = ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;
|
||||||
|
#endif
|
||||||
|
switch (ledc_timer_config(&ledc_timer)) {
|
||||||
|
case ESP_ERR_INVALID_ARG: ESP_LOGE(TAG, "ledc_timer_config() parameter error"); break;
|
||||||
|
case ESP_FAIL: ESP_LOGE(TAG, "ledc_timer_config() Can not find a proper pre-divider number base on the given frequency and the current duty_resolution"); break;
|
||||||
|
case ESP_OK: if (ledc_channel_config(&ledc_channel) == ESP_ERR_INVALID_ARG) {
|
||||||
|
ESP_LOGE(TAG, "ledc_channel_config() parameter error");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
camera_config_t config;
|
camera_config_t config;
|
||||||
config.ledc_channel = LEDC_CHANNEL_0;
|
config.ledc_channel = LEDC_CHANNEL_0;
|
||||||
config.ledc_timer = LEDC_TIMER_0;
|
config.ledc_timer = LEDC_TIMER_0;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "esp_camera.h"
|
#include "esp_camera.h"
|
||||||
#include "img_converters.h"
|
#include "img_converters.h"
|
||||||
#include "fb_gfx.h"
|
#include "fb_gfx.h"
|
||||||
|
#include "driver/ledc.h"
|
||||||
//#include "camera_index.h"
|
//#include "camera_index.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
@ -47,6 +48,16 @@ static const char* TAG = "camera_httpd";
|
||||||
#define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED)
|
#define FACE_COLOR_PURPLE (FACE_COLOR_BLUE | FACE_COLOR_RED)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_LED_ILLUMINATOR_ENABLED
|
||||||
|
int led_duty = 0;
|
||||||
|
bool isStreaming = false;
|
||||||
|
#ifdef CONFIG_LED_LEDC_LOW_SPEED_MODE
|
||||||
|
#define CONFIG_LED_LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||||
|
#else
|
||||||
|
#define CONFIG_LED_LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
httpd_req_t *req;
|
httpd_req_t *req;
|
||||||
size_t len;
|
size_t len;
|
||||||
|
@ -59,6 +70,7 @@ static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %
|
||||||
|
|
||||||
httpd_handle_t stream_httpd = NULL;
|
httpd_handle_t stream_httpd = NULL;
|
||||||
httpd_handle_t camera_httpd = NULL;
|
httpd_handle_t camera_httpd = NULL;
|
||||||
|
|
||||||
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
||||||
static mtmn_config_t mtmn_config = {0};
|
static mtmn_config_t mtmn_config = {0};
|
||||||
static int8_t detection_enabled = 0;
|
static int8_t detection_enabled = 0;
|
||||||
|
@ -225,6 +237,19 @@ static int run_face_recognition(dl_matrix3du_t *image_matrix, box_array_t *net_b
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_LED_ILLUMINATOR_ENABLED
|
||||||
|
void enable_led(bool en) { // Turn LED On or Off
|
||||||
|
int duty = en ? led_duty : 0;
|
||||||
|
if (en && isStreaming && (led_duty > CONFIG_LED_MAX_INTENSITY)) {
|
||||||
|
duty = CONFIG_LED_MAX_INTENSITY;
|
||||||
|
}
|
||||||
|
ledc_set_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL, duty);
|
||||||
|
ledc_update_duty(CONFIG_LED_LEDC_SPEED_MODE, CONFIG_LED_LEDC_CHANNEL);
|
||||||
|
ESP_LOGI(TAG, "Set LED intensity to %d", duty);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
|
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;
|
jpg_chunking_t *j = (jpg_chunking_t *)arg;
|
||||||
if(!index){
|
if(!index){
|
||||||
|
@ -242,7 +267,15 @@ static esp_err_t capture_handler(httpd_req_t *req){
|
||||||
esp_err_t res = ESP_OK;
|
esp_err_t res = ESP_OK;
|
||||||
int64_t fr_start = esp_timer_get_time();
|
int64_t fr_start = esp_timer_get_time();
|
||||||
|
|
||||||
|
#ifdef CONFIG_LED_ILLUMINATOR_ENABLED
|
||||||
|
enable_led(true);
|
||||||
|
vTaskDelay(150 / portTICK_PERIOD_MS); // The LED needs to be turned on ~150ms before the call to esp_camera_fb_get()
|
||||||
|
fb = esp_camera_fb_get(); // or it won't be visible in the frame. A better way to do this is needed.
|
||||||
|
enable_led(false);
|
||||||
|
#else
|
||||||
fb = esp_camera_fb_get();
|
fb = esp_camera_fb_get();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!fb) {
|
if (!fb) {
|
||||||
ESP_LOGE(TAG, "Camera capture failed");
|
ESP_LOGE(TAG, "Camera capture failed");
|
||||||
httpd_resp_send_500(req);
|
httpd_resp_send_500(req);
|
||||||
|
@ -359,11 +392,17 @@ static esp_err_t stream_handler(httpd_req_t *req){
|
||||||
|
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
#ifdef CONFIG_LED_ILLUMINATOR_ENABLED
|
||||||
|
enable_led(true);
|
||||||
|
isStreaming = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
while(true){
|
while(true){
|
||||||
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
||||||
detected = false;
|
detected = false;
|
||||||
face_id = 0;
|
face_id = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fb = esp_camera_fb_get();
|
fb = esp_camera_fb_get();
|
||||||
if (!fb) {
|
if (!fb) {
|
||||||
ESP_LOGE(TAG, "Camera capture failed");
|
ESP_LOGE(TAG, "Camera capture failed");
|
||||||
|
@ -489,6 +528,12 @@ static esp_err_t stream_handler(httpd_req_t *req){
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef CONFIG_LED_ILLUMINATOR_ENABLED
|
||||||
|
isStreaming = false;
|
||||||
|
enable_led(false);
|
||||||
|
#endif
|
||||||
|
|
||||||
last_frame = 0;
|
last_frame = 0;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -556,6 +601,9 @@ static esp_err_t cmd_handler(httpd_req_t *req){
|
||||||
else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(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, "wb_mode")) res = s->set_wb_mode(s, val);
|
||||||
else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(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; if (isStreaming) enable_led(true); }
|
||||||
|
#endif
|
||||||
|
|
||||||
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
||||||
else if(!strcmp(variable, "face_detect")) {
|
else if(!strcmp(variable, "face_detect")) {
|
||||||
|
@ -619,7 +667,11 @@ static esp_err_t status_handler(httpd_req_t *req){
|
||||||
p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
|
p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
|
||||||
p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
|
p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
|
||||||
p+=sprintf(p, "\"colorbar\":%u", s->status.colorbar);
|
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
|
||||||
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
#if CONFIG_ESP_FACE_DETECT_ENABLED
|
||||||
p+=sprintf(p, ",\"face_detect\":%u", detection_enabled);
|
p+=sprintf(p, ",\"face_detect\":%u", detection_enabled);
|
||||||
#if CONFIG_ESP_FACE_RECOGNITION_ENABLED
|
#if CONFIG_ESP_FACE_RECOGNITION_ENABLED
|
||||||
|
|
|
@ -531,6 +531,12 @@
|
||||||
<label class="slider" for="colorbar"></label>
|
<label class="slider" for="colorbar"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group" id="led-group">
|
||||||
|
<label for="led_intensity">LED Intensity</label>
|
||||||
|
<div class="range-min">0</div>
|
||||||
|
<input type="range" id="led_intensity" min="0" max="255" value="0" class="default-action">
|
||||||
|
<div class="range-max">255</div>
|
||||||
|
</div>
|
||||||
<div class="input-group" id="face_detect-group">
|
<div class="input-group" id="face_detect-group">
|
||||||
<label for="face_detect">Face Detection</label>
|
<label for="face_detect">Face Detection</label>
|
||||||
<div class="switch">
|
<div class="switch">
|
||||||
|
@ -612,6 +618,8 @@ document.addEventListener('DOMContentLoaded', function (event) {
|
||||||
value ? show(wb) : hide(wb)
|
value ? show(wb) : hide(wb)
|
||||||
} else if(el.id === "face_recognize"){
|
} else if(el.id === "face_recognize"){
|
||||||
value ? enable(enrollButton) : disable(enrollButton)
|
value ? enable(enrollButton) : disable(enrollButton)
|
||||||
|
} else if(el.id == "led_intensity"){
|
||||||
|
value > -1 ? show(ledGroup) : hide(ledGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -670,6 +678,7 @@ document.addEventListener('DOMContentLoaded', function (event) {
|
||||||
const enrollButton = document.getElementById('face_enroll')
|
const enrollButton = document.getElementById('face_enroll')
|
||||||
const closeButton = document.getElementById('close-stream')
|
const closeButton = document.getElementById('close-stream')
|
||||||
const saveButton = document.getElementById('save-still')
|
const saveButton = document.getElementById('save-still')
|
||||||
|
const ledGroup = document.getElementById('led-group')
|
||||||
|
|
||||||
const stopStream = () => {
|
const stopStream = () => {
|
||||||
window.stop();
|
window.stop();
|
||||||
|
|
Binary file not shown.
|
@ -560,6 +560,12 @@
|
||||||
<label class="slider" for="colorbar"></label>
|
<label class="slider" for="colorbar"></label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group" id="led-group">
|
||||||
|
<label for="led_intensity">LED Intensity</label>
|
||||||
|
<div class="range-min">0</div>
|
||||||
|
<input type="range" id="led_intensity" min="0" max="255" value="0" class="default-action">
|
||||||
|
<div class="range-max">255</div>
|
||||||
|
</div>
|
||||||
<div class="input-group" id="face_detect-group">
|
<div class="input-group" id="face_detect-group">
|
||||||
<label for="face_detect">Face Detection</label>
|
<label for="face_detect">Face Detection</label>
|
||||||
<div class="switch">
|
<div class="switch">
|
||||||
|
@ -639,6 +645,8 @@ document.addEventListener('DOMContentLoaded', function (event) {
|
||||||
value ? show(wb) : hide(wb)
|
value ? show(wb) : hide(wb)
|
||||||
} else if(el.id === "face_recognize"){
|
} else if(el.id === "face_recognize"){
|
||||||
value ? enable(enrollButton) : disable(enrollButton)
|
value ? enable(enrollButton) : disable(enrollButton)
|
||||||
|
} else if(el.id == "led_intensity"){
|
||||||
|
value > -1 ? show(ledGroup) : hide(ledGroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -697,6 +705,7 @@ document.addEventListener('DOMContentLoaded', function (event) {
|
||||||
const enrollButton = document.getElementById('face_enroll')
|
const enrollButton = document.getElementById('face_enroll')
|
||||||
const closeButton = document.getElementById('close-stream')
|
const closeButton = document.getElementById('close-stream')
|
||||||
const saveButton = document.getElementById('save-still')
|
const saveButton = document.getElementById('save-still')
|
||||||
|
const ledGroup = document.getElementById('led-group')
|
||||||
|
|
||||||
const stopStream = () => {
|
const stopStream = () => {
|
||||||
window.stop();
|
window.stop();
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue