ESP32-CAM: aggiornare CameraWebServer con la funzionalità di flash – 2

Spread the love

ESP32-CAM clone upgrade web interface with flash light
ESP32-CAM clone upgrade web interface with flash light

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.

ESP32 CAM pinout
ESP32 CAM pinout

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 Arduino
File --> Preferences

ESP32 S2 download Arduino IDE preferences folder
ESP32 S2 download Arduino IDE preferences folder

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

ESP32-cam IP camera flash on 3D printer with octoprint
ESP32-cam IP camera flash on 3D printer with octoprint
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 modePower
Stand by80mHa
In streaming100~160mAh
In streaming with flash270mAh

Quindi per usarlo correttamente è necessario collegare il dispositivo a una fonte di alimentazione esterna

esp32-cam upload sketch and normal connection schema 5v external power
esp32-cam upload sketch and normal connection schema 5v external power

Molto luminoso il LED, ma l’amperaggio è rilevante.

ESP32-cam IP camera flash opened with tester
ESP32-cam IP camera flash opened with tester

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.

New ESP32 cam (framework 2.x.x) CameraWebServer with flash
New ESP32 cam (framework 2.x.x) CameraWebServer with flash

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

Firmware con funzionalità aggiuntive.


Spread the love

2 Risposte

  1. Massimo Imovilli ha detto:

    Grazie mille, sono riuscito a fare tutto.
    Mi rimane solo da progettare e stampare un mini case per il montaggio sulla mia Ghost5.
    Ciao ciao Octoprint.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *