Questo esempio funziona con la versione 1.0.6 del framework ESP32., per la versione 2.x.x vai alla sezione che trovi sotto
Uso questa esp32-cam per tutti i miei sistemi di monitoraggio e anche per fare dei video con più finestre, penso che la funzione led integrata nella scheda esp32-cam possa essere molto utile, perciò ho pensato di aggiungere il controllo sull’interfaccia web.
Ma ricorda che il pin flash è lo stesso di SD, quindi se vuoi funzionalità led devi evitare di usare l’SD.
Ora puoi fare riferimento all’articolo “ESP32-CAM: piedinatura, specifiche e configurazione dell’Arduino IDE” per avere ulteriori informazioni su come caricare gli sketch. Nel pinout puoi trovare Flash light su GPIO4 .
Puoi trovare l'ESP32-CAM su ESP32 Dev Kit v1 - TTGO T-Display 1.14 ESP32 - NodeMCU V3 V2 ESP8266 Lolin32 - NodeMCU ESP-32S - WeMos Lolin32 - WeMos Lolin32 mini - ESP32-CAM programmer - ESP32-CAM bundle - ESP32-WROOM-32 - ESP32-S
Recuperare lo sketch per consentirne la modifica
Per prima cosa scarica l’ sketch CameraWebServer da qui o apri l’esempio in Arduino o direttamente dall’installazione del core di esp32 con questi passaggi:
Recupera il percorso delle preferenze dal tuo IDE ArduinoFile --> Preferences
da qui vai a
<arduino-preferences-path>\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32\examples\Camera\CameraWebServer
Puoi trovare in quella cartella 4 file
CameraWebServer.ino
: lo sketch principale;app_httpd.cpp
: praticamente è il back-end dell’applicazione;camera_pins.h
: contiene lo schema dei pin per tutte le varianti;camera_index.h
: contiene la pagina web in versione gzipped base64.
Estrai la pagina HTML
Ora, come puoi vedere, non troviamo la pagina html, ma puoi usare questa utility per trasformare l’array HEX del file camera_index.h
nel classico file HTML.
Convertitore online: da array HEX a file
copia i valori dell’array HEX e incollali nella textarea (dalla prima parentesi {
all’ultima }
esclusa) e inserisci index_ov2640.html.gz
come nome file, scarica il file GZip, estrai index_ov2640.html
e mettilo nella tua cartella, apri con un editor.
Ora abbiamo tutto il materiale per applicare una modifica al CameraWebServer.
Nella pagina html trova l’ultimo slider (face_recognize-group), come nel seguente codice
<div class="input-group" id="face_recognize-group">
<label for="face_recognize">Face Recognition</label>
<div class="switch">
<input id="face_recognize" type="checkbox" class="default-action">
<label class="slider" for="face_recognize"></label>
</div>
</div>
<section id="buttons">
<button id="get-still">Get Still</button>
<button id="toggle-stream">Start Stream</button>
<button id="face_enroll" class="disabled" disabled="disabled">Enroll Face</button>
</section>
e aggiungi un flash-group in questo modo
<div class="input-group" id="face_recognize-group">
<label for="face_recognize">Face Recognition</label>
<div class="switch">
<input id="face_recognize" type="checkbox" class="default-action">
<label class="slider" for="face_recognize"></label>
</div>
</div>
<div class="input-group" id="flash-group">
<label for="flash">Flash</label>
<div class="switch">
<input id="flash" type="checkbox" class="default-action">
<label class="slider" for="flash"></label>
</div>
</div>
<section id="buttons">
<button id="get-still">Get Still</button>
<button id="toggle-stream">Start Stream</button>
<button id="face_enroll" class="disabled" disabled="disabled">Enroll Face</button>
</section>
Back-end end-point
Tutti gli elementi con class default-action hanno un listener
// Attach default on change action
document
.querySelectorAll('.default-action')
.forEach(el => {
el.onchange = () => updateConfig(el)
})
che al onchange chiama una funzione updateConfig
function updateConfig (el) {
let value
switch (el.type) {
case 'checkbox':
value = el.checked ? 1 : 0
break
case 'range':
case 'select-one':
value = el.value
break
case 'button':
case 'submit':
value = '1'
break
default:
return
}
const query = `${baseHost}/control?var=${el.id}&val=${value}`
fetch(query)
.then(response => {
console.log(`request to ${query} finished, status: ${response.status}`)
})
}
questa funzione fa un GET (fare riferimento al tutorial “Come creare un REST server con esp8266 o esp32”) che chiama un handler specificato nel file app_https.cpp
static esp_err_t cmd_handler(httpd_req_t *req){
char* buf;
size_t buf_len;
char variable[32] = {0,};
char value[32] = {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) {
if (httpd_query_key_value(buf, "var", variable, sizeof(variable)) == ESP_OK &&
httpd_query_key_value(buf, "val", value, sizeof(value)) == ESP_OK) {
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
} else {
free(buf);
httpd_resp_send_404(req);
return ESP_FAIL;
}
free(buf);
} else {
httpd_resp_send_404(req);
return ESP_FAIL;
}
int val = atoi(value);
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);
}
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);
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 if(!strcmp(variable, "flash") ) {
pinMode(4, OUTPUT);
digitalWrite(4, atoi(value));
flash_enabled = atoi(value);
}
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);
}
L’handler gestisce tutte le azioni dell’interfaccia web, possiamo aggiungere anche la gestione del flash, un digitalWrite
sul pin 4 (led integrato) con il valore dello slider.
Puoi chiamare l’end-point direttamente dal browser con
http://<camera-ip>/control?var=flash&val=1
per attivare il flash, e
http://<camera-ip>/control?var=flash&val=0
per disattivare.
Imposta uno stato iniziale
Impostiamo anche il valore sulla variabile flash_enabled, dichiarata con l’altra variabile di stato
static mtmn_config_t mtmn_config = {0};
static int8_t detection_enabled = 0;
static int8_t recognition_enabled = 0;
static int8_t flash_enabled = 0;
static int8_t is_enrolling = 0;
static face_id_list id_list = {0};
utilizzata per ripristinare il valore sull’end point in GET status
, ed a sua volta gestito con status_handler
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++ = '{';
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, "\"vflip\":%u,", s->status.vflip);
p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar);
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+=sprintf(p, "\"flash\":%u", flash_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));
}
per mostrare la modifica devi solo inserire l’url sul browser (è in GET)
http://<camera-ip>/status
con questo risultato
{
"framesize": 4,
"quality": 10,
"brightness": 0,
"contrast": 0,
"saturation": 0,
"sharpness": 0,
"special_effect": 0,
"wb_mode": 0,
"awb": 1,
"awb_gain": 1,
"aec": 1,
"aec2": 0,
"ae_level": 0,
"aec_value": 168,
"agc": 1,
"agc_gain": 0,
"gainceiling": 0,
"bpc": 0,
"wpc": 1,
"raw_gma": 1,
"lenc": 1,
"vflip": 0,
"hmirror": 0,
"dcw": 1,
"colorbar": 0,
"face_detect": 0,
"face_enroll": 0,
"face_recognize": 0,
"flash": 0
}
il quale viene chiamato all’inizializzazione dell’interfaccia web
// read initial values
fetch(`${baseHost}/status`)
.then(function (response) {
return response.json()
})
.then(function (state) {
document
.querySelectorAll('.default-action')
.forEach(el => {
updateValue(el, state[el.id], false)
})
})
ed imposta il valore corretto dello slider al primo caricamento.
Riconversione a HEX array
Quando effettui la modifica alla pagina, devi rigenerare l’array HEX da file in formato GZipped, per semplificare l’operazione sul mio “Online converter: File to (cpp) gzip byte array” genero il filerray “semplice” e gzippato in un unico passaggio.
Ricorda che il dispositivo richiede una discreta quantità di energia
Operation mode | Power |
---|---|
Stand by | 80mHa |
In streaming | 100~160mAh |
In streaming with flash | 270mAh |
Quindi per usarlo correttamente è necessario collegare il dispositivo a una fonte di alimentazione esterna
Molto luminoso il LED, ma l’amperaggio è rilevante.
Utilizzare il CameraWebServer del Framework versione 2.x.x
Nella nuova versione, Espressiff ha modificato la libreria di riconoscimento facciale e aggiunto la gestione della funzionalità flash nel framework IDF.
Per attivare tali funzionalità, il processo è abbastanza semplice.
È necessario aggiungere queste righe dopo le impostazioni camera_config_t config;
nel file CameraWebServer.ino
principale.
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.frame_size = FRAMESIZE_UXGA;
config.pixel_format = PIXFORMAT_JPEG; // for streaming
//config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.jpeg_quality = 12;
config.fb_count = 1;
const int pwmfreq = 50000; // 50K pwm frequency
const int pwmresolution = 9; // duty cycle bit range
ledcSetup(config.ledc_channel, pwmfreq, pwmresolution); // configure LED PWM channel
ledcAttachPin(4, config.ledc_channel); // attach the GPIO pin to the channel
E nella prima parte del file app_attpd.cpp
dopo le include questi define.
//
// LED Illuminator
//
#define CONFIG_LED_ILLUMINATOR_ENABLED
#define CONFIG_LED_LEDC_PIN 4
#define CONFIG_LED_MAX_INTENSITY 255
//#define CONFIG_LED_LEDC_LOW_SPEED_MODE
// CONFIG_LED_LEDC_HIGH_SPEED_MODE is not set
#define CONFIG_LED_LEDC_TIMER LEDC_TIMER_0
#define CONFIG_LED_LEDC_CHANNEL LEDC_CHANNEL_0
// end of LED Illuminator
Il risultato è in questo screenshot.
Imposta un IP statico
Un’altra cosa molto utile è impostare l’IP statico, per farlo basta solo aggiungere questa riga prima di WiFi.begin
IPAddress ip(192,168,1,40);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
WiFi.config(ip, gateway, subnet);
Grazie
- ESP32-CAM: piedinatura, specifiche e configurazione dell’Arduino IDE
- ESP32-CAM: aggiornare CameraWebServer con la funzionalità di flash
- ESP32-CAM: controllare il CameraWebServer dalla tua pagina web
Firmware con funzionalità aggiuntive.