Bonjour,
Cela fait maintenant quelques jours que je me suis lancé dans un projet avec un ESP32-CAM: faire un robot pilotable depuis une application Android (que je n'ai pas encore réalisée) et y afficher également le Stream de la camera en direct.
Le robot communiquerais avec le téléphone par Wi-Fi en mode AP.
Mon idée, après pas mal de recherche est de diffuser l'image à une adresse Uri : 192.168.4.1/cam, pour la récupérer dans l'application.
Ensuite, d'autres adresses serviraient à envoyer des requêtes pour donner des instruction au robot.
Par exemple 192.168.4.1/on pour allumer une LED (c'est juste pour tester la communication pour l'instant) et 192.168.4.1/off pour l'éteindre.
J'utilise la bibliothèque "esp_http_server".
Ceci fonctionne en partie (je teste avec un navigateur), par contre, lorsque j'affiche la camera, il m'est impossible d'envoyer une requête pour allumer ou éteindre la lampe.
Je suis obligé de quitter la page 192.168.4.1/cam puis envoyer ma requête à 192.168.4.1/on.
Ma théorie est que je dois utiliser une bibliothèque Asynchrone (ESPAsyncWebServer par exemple) pour gérer plusieurs requêtes à la fois.
Le problèmes est que après beaucoup de recherches, je n'ai pas trouvé le moyen de streamer la camera avec cette bibliothèque.
Voici mon code actuel:
main.cpp
#include "Arduino.h"
#include "camera.h"
#include "connection.h"
#include "serveur.h"
const int pin_led = 4;
void setupPin(){
pinMode(pin_led, OUTPUT);
digitalWrite(pin_led, LOW);
}
void ledOn(){
digitalWrite(pin_led,HIGH);
}
void ledOff(){
digitalWrite(pin_led,LOW);
}
void setup() {
setupPin();
initConnection();
startCameraServer();
initHttpd();
}
void loop() {
delay(1);
}
connection.cpp
#include <WiFi.h>
const char* ssid = "ESP32BOT";
const char* password = "Ez8AFt24";
//initialise la connection WiFi en mode AP
void initConnection(){
WiFi.softAP(ssid, password);
}
camera.cpp
#include "Arduino.h"
#include "esp_camera.h"
#include "esp_http_server.h"
#include "esp_timer.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h" //disable brownout problems
//définitions des pins GPIO de la camera (modèle AI-Thinker)
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
//configuration de la camera
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAMESIZE_VGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG
.jpeg_quality = 12, //0-63 lower number means higher quality
.fb_count = 1, //if more than one, i2s runs in continuous mode. Use only with JPEG
.grab_mode = CAMERA_GRAB_WHEN_EMPTY//CAMERA_GRAB_LATEST. Sets when buffers should be filled
};
//initialise la camera
esp_err_t camera_init(){
//power up the camera if PWDN pin is defined
if(PWDN_GPIO_NUM != -1){
pinMode(PWDN_GPIO_NUM, OUTPUT);
digitalWrite(PWDN_GPIO_NUM, LOW);
}
//initialize the camera
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
Serial.println("Camera Init Failed");
return err;
}
return ESP_OK;
}
void startCameraServer(){
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
camera_config.pin_sscb_sda = SIOD_GPIO_NUM;
camera_config.pin_sscb_scl = SIOC_GPIO_NUM;
esp_err_t err = esp_camera_init(&camera_config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
}
serveur.cpp
#include "Arduino.h"
#include "esp_http_server.h"
#include "camera.h"
#include "esp_camera.h"
#include "main.h"
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
//définition du stream
#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\n\r\n";
//configuration du stream
esp_err_t jpg_stream_httpd_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len;
uint8_t * _jpg_buf;
char * part_buf[64];
static int64_t last_frame = 0;
if(!last_frame) {
last_frame = esp_timer_get_time();
}
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if(res != ESP_OK){
return res;
}
while(true){
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
res = ESP_FAIL;
break;
}
if(fb->format != PIXFORMAT_JPEG){
bool jpeg_0onverted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
if(!jpeg_converted){
Serial.println("JPEG compression failed");
esp_camera_fb_return(fb);
res = ESP_FAIL;
}
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
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, 64, _STREAM_PART, _jpg_buf_len);
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(fb->format != PIXFORMAT_JPEG){
free(_jpg_buf);
}
esp_camera_fb_return(fb);
if(res != ESP_OK){
break;
}
int64_t fr_end = esp_timer_get_time();
int64_t frame_time = fr_end - last_frame;
last_frame = fr_end;
frame_time /= 1000;
ESP_LOGI(TAG, "MJPG: %uKB %ums (%.1ffps)",
(uint32_t)(_jpg_buf_len/1024),
(uint32_t)frame_time, 1000.0 / (uint32_t)frame_time);
}
last_frame = 0;
return res;
}
esp_err_t on_handler(httpd_req_t *req)
{
/* Allume la lampe */
ledOn();
const char resp[] = "LED ON";
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t off_handler(httpd_req_t *req)
{
/* Eteint la lampe */
ledOff();
const char resp[] = "LED OFF";
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t post_handler(httpd_req_t *req)
{
/* Destination buffer for content of HTTP POST request.
* httpd_req_recv() accepts char* only, but content could
* as well be any binary data (needs type casting).
* In case of string data, null termination will be absent, and
* content length would give length of string */
char content[100];
/* Truncate if content length larger than the buffer */
size_t recv_size = min(req->content_len, sizeof(content));
int ret = httpd_req_recv(req, content, recv_size);
if (ret <= 0) { /* 0 return value indicates connection closed */
/* Check if timeout occurred */
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
/* In case of timeout one can choose to retry calling
* httpd_req_recv(), but to keep it simple, here we
* respond with an HTTP 408 (Request Timeout) error */
httpd_resp_send_408(req);
}
/* In case of error, returning ESP_FAIL will
* ensure that the underlying socket is closed */
return ESP_FAIL;
}
/* Send a simple response */
const char resp[] = "URI POST Response";
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
httpd_uri_t uri_cam = {
.uri = "/cam",
.method = HTTP_GET,
.handler = jpg_stream_httpd_handler,
.user_ctx = NULL
};
/* URI handler structure for GET /uri */
httpd_uri_t uri_on = {
.uri = "/on",
.method = HTTP_GET,
.handler = on_handler,
.user_ctx = NULL
};
httpd_uri_t uri_off = {
.uri = "/off",
.method = HTTP_GET,
.handler = off_handler,
.user_ctx = NULL
};
/* URI handler structure for POST /uri */
httpd_uri_t uri_post = {
.uri = "/uri",
.method = HTTP_POST,
.handler = post_handler,
.user_ctx = NULL
};
void setupUri(){
if (httpd_start(&server, &config) == ESP_OK) {
httpd_register_uri_handler(server, &uri_on);
httpd_register_uri_handler(server, &uri_off);
httpd_register_uri_handler(server, &uri_post);
httpd_register_uri_handler(server, &uri_cam);
}
}
void initHttpd(){
config.server_port = 80;
setupUri();
}
Merci d'avoir lu !
Melvin