I wish to make use of the arduino standard library of http server to make dual communication between the web page and the esp32 server with camera streaming through WiFi.
It seems I got my result in the screen, I got the camera video as well as some data (LED PWM value as well as antenna received signal strength) from the esp32, but I found continuous and intermittent errors of no response (status 404) in the network traffics of the web page. (Tests were done on both AP mode and Client mode of the esp32, and the results are the same)
My guess is that there may be crash between the libraries of esp_http_server, WebServer and WiFiClient, but it is out my knowledge limit to further investigate this. So I wish to find answer from you. Appreciate for any comment.
Here, I post my code as follow: (Acknowledgement: I modified the source code from "Random Nerd" to achieve the dual communication.)
/*********
Modified from project at https://RandomNerdTutorials.com
From Project: ESP32-CAM Remote Controlled Car Robot Web Server
https://randomnerdtutorials.com/esp32-cam-car-robot-web-server/
HM, 5 Nov 2023
*********/
// Sample server commands: (assume ip address of the ESP32 is 192.168.8.5)
// 192.168.8.5/control?var=forward&val=1
// 192.168.8.5/control?var=led_PWM&val=10
// If more httpd_query_key_value() is added, use "&" to seperate key/value pair
// 192.168.8.5:81/stream
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
// #include <AsyncTCP.h>
// #include <ESPAsyncWebSrv.h> XXXX
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <WiFiClient.h>
#include <WebServer.h>
WebServer server(80);
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "fb_gfx.h"
#include "soc/soc.h" // disable brownout problems
#include "soc/rtc_cntl_reg.h" // disable brownout problems
#include "esp_http_server.h"
#include "esp32-hal-ledc.h" // For Buildin LED
#define Flashlight 4 // Build-in flashlight of ESP32-CAM Board
const int ledFreq = 1000;
const int ledChannel = 4;
const int ledResolution = 12;
int led_intensity = 0;
// Replace with your network credentials
const char* ssid = "SSID for Android phone or PC";
const char* password = "123456";
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM_B
//#define CAMERA_MODEL_WROVER_KIT
#if defined(CAMERA_MODEL_WROVER_KIT)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 21
#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 19
#define Y4_GPIO_NUM 18
#define Y3_GPIO_NUM 5
#define Y2_GPIO_NUM 4
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 25
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 17
#define VSYNC_GPIO_NUM 22
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#elif defined(CAMERA_MODEL_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
#elif defined(CAMERA_MODEL_M5STACK_PSRAM_B)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM 15
#define XCLK_GPIO_NUM 27
#define SIOD_GPIO_NUM 22
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 19
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 5
#define Y4_GPIO_NUM 34
#define Y3_GPIO_NUM 35
#define Y2_GPIO_NUM 32
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define PCLK_GPIO_NUM 21
#else
#error "Camera model not selected"
#endif
#define MOTOR_1_PIN_1 14
#define MOTOR_1_PIN_2 15
#define MOTOR_2_PIN_1 13
#define MOTOR_2_PIN_2 12
#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";
httpd_handle_t camera_httpd = NULL;
httpd_handle_t stream_httpd = NULL;
static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>ESP32-CAM Robot</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
table { margin-left: auto; margin-right: auto; }
td { padding: 8 px; }
.button {
background-color: #2f4468;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 18px;
margin: 6px 3px;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
img { width: auto ;
max-width: 100% ;
height: auto ;
}
</style>
</head>
<body>
<h1>ESP32-CAM Robot</h1>
<img src="" id="photo" >
<table>
<tr>
<td colspan="3" align="center">
<button class="button" id="button_forward" ontouchstart="button_state_forward = true;" >forward</button>
</td>
</tr>
<tr>
<td align="center">
<button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Left</button>
</td>
<td align="center">
<button class="button" onmousedown="toggleCheckbox('stop');" ontouchstart="toggleCheckbox('stop');">Stop</button></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Right</button>
</td>
</tr>
<tr>
<td colspan="3" align="center">
<button class="button" onmousedown="toggleCheckbox('backward');" ontouchstart="toggleCheckbox('backward');" onmouseup="toggleCheckbox('stop');" ontouchend="toggleCheckbox('stop');">Backward</button>
</td>
</tr>
</table>
<div align="center">
<input type="range" class="slider" id="LEDslider" min="0" max="255" value="0"
onchange="try{
fetch(document.location.origin+'/control?var=led_PWM&val='+this.value);
}catch(e){}">
</div>
<div class="LED_feedback_value">
<h4>The feedback of LED intensity: <span id="LED_feedback_value">0</span></h4>
</div>
<div class="RSSI">
<h4>The Received Signal Strength (RSSI): <span id="RSSI_feedback_value">N/A </span>dBm</h4><br>
</div>
<script>
var counter1 = 0;
let panelButtons = 0;
var button_state_forward = false;
function toggleCheckbox(x) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/control?var=" + x + "&val=0", true); // URL = server_ip_address/control?var=xxx&val=xxx
xhr.send();
}
setInterval(function(){
controlPanel();
counter1+=1;
if (counter1==2){
getLEDval(); //It was found that the 1st XML Request will be lost
getLEDval(); //So a 2nd XML Request will be repeated.
}
if (counter1==4){
counter1=0;
getRSSI();
getRSSI();
}
},200);
function controlPanel(){
fetch(document.location.origin+'/control?var=led_PWM&val='+ document.getElementById("LEDslider").value);
if(button_state_forward){
toggleCheckbox('forward');
button_state_forward=false; // URL = server_ip_address/control?var=xxx&val=xxx
}
}
function getLEDval(){
var xhttpr = new XMLHttpRequest();
xhttpr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
document.getElementById("LED_feedback_value").innerHTML = this.responseText;
}
}
xhttpr.open("GET", "readLED", true);
xhttpr.send();
}
function getRSSI(){
var xhttpr = new XMLHttpRequest();
xhttpr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
document.getElementById("RSSI_feedback_value").innerHTML = this.responseText;
}
}
xhttpr.open("GET", "readRSSI", true);
xhttpr.send();
}
window.onload = document.getElementById("photo").src = window.location.href.slice(0, -1) + ":81/stream";
// setInterval(controlPanel,1000); // Javascript does not support multiple setInterval()
</script>
</body>
</html>
)rawliteral";
static esp_err_t index_handler(httpd_req_t *req){
httpd_resp_set_type(req, "text/html");
return httpd_resp_send(req, (const char *)INDEX_HTML, strlen(INDEX_HTML));
}
static esp_err_t stream_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
size_t _jpg_buf_len = 0;
uint8_t * _jpg_buf = NULL;
char * part_buf[64];
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;
} else {
if(fb->width > 400){
if(fb->format != PIXFORMAT_JPEG){
bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
esp_camera_fb_return(fb);
fb = NULL;
if(!jpeg_converted){
Serial.println("JPEG compression failed");
res = ESP_FAIL;
}
} else {
_jpg_buf_len = fb->len;
_jpg_buf = fb->buf;
}
}
}
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(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
if(fb){
esp_camera_fb_return(fb);
fb = NULL;
_jpg_buf = NULL;
} else if(_jpg_buf){
free(_jpg_buf);
_jpg_buf = NULL;
}
if(res != ESP_OK){
break;
}
//Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
}
return res;
}
static esp_err_t cmd_handler(httpd_req_t *req){
char* buf;
size_t buf_len;
char variable[32] = {0,};
char value[32] = {0,}; // Assume max. of 32 characters??
// Serial.println("Received a command!");
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;
}
sensor_t * s = esp_camera_sensor_get();
int res = 0;
/* Control Decode */
if(!strcmp(variable, "forward")) {
Serial.println("Forward");
digitalWrite(MOTOR_1_PIN_1, 1);
digitalWrite(MOTOR_1_PIN_2, 0);
digitalWrite(MOTOR_2_PIN_1, 1);
digitalWrite(MOTOR_2_PIN_2, 0);
}
else if(!strcmp(variable, "left")) {
Serial.println("Left");
digitalWrite(MOTOR_1_PIN_1, 0);
digitalWrite(MOTOR_1_PIN_2, 1);
digitalWrite(MOTOR_2_PIN_1, 1);
digitalWrite(MOTOR_2_PIN_2, 0);
}
else if(!strcmp(variable, "right")) {
Serial.println("Right");
digitalWrite(MOTOR_1_PIN_1, 1);
digitalWrite(MOTOR_1_PIN_2, 0);
digitalWrite(MOTOR_2_PIN_1, 0);
digitalWrite(MOTOR_2_PIN_2, 1);
}
else if(!strcmp(variable, "backward")) {
Serial.println("Backward");
digitalWrite(MOTOR_1_PIN_1, 0);
digitalWrite(MOTOR_1_PIN_2, 1);
digitalWrite(MOTOR_2_PIN_1, 0);
digitalWrite(MOTOR_2_PIN_2, 1);
}
else if(!strcmp(variable, "stop")) {
Serial.println("Stop");
digitalWrite(MOTOR_1_PIN_1, 0);
digitalWrite(MOTOR_1_PIN_2, 0);
digitalWrite(MOTOR_2_PIN_1, 0);
digitalWrite(MOTOR_2_PIN_2, 0);
}
else if(!strcmp(variable, "led_PWM")){
led_intensity = atoi(value)/4; // val =
Serial.printf("Command: LED PWM = %u\n",led_intensity); // val
ledcWrite(Flashlight, led_intensity); // val
}
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);
}
void startCameraServer(){
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 80;
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_uri_t cmd_uri = {
.uri = "/control",
.method = HTTP_GET,
.handler = cmd_handler,
.user_ctx = NULL
};
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
if (httpd_start(&camera_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(camera_httpd, &index_uri);
httpd_register_uri_handler(camera_httpd, &cmd_uri);
}
config.server_port += 1;
config.ctrl_port += 1;
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
}
}
void handleRSSI() {
server.send(200, "text/plane", String(WiFi.RSSI())); //Send LED value only to client ajax request
Serial.println("RSSI request received and XMLHttpResponse sent!");
// Serial.printf("RSSi: %ld dBm\n",WiFi.RSSI())
}
void handleLED() {
server.send(200, "text/plane", String(led_intensity)); //Send LED value only to client ajax request
Serial.println("LED PWM request received and XMLHttpResponse sent!");
}
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
ledcSetup(ledChannel, ledFreq, ledResolution); // Setup LED PWM freq & resolution
ledcAttachPin(Flashlight, 4);
pinMode(MOTOR_1_PIN_1, OUTPUT);
pinMode(MOTOR_1_PIN_2, OUTPUT);
pinMode(MOTOR_2_PIN_1, OUTPUT);
pinMode(MOTOR_2_PIN_2, OUTPUT);
Serial.begin(115200);
Serial.setDebugOutput(false);
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.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
// Wi-Fi connection
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("Camera Stream Ready! Go to: http://");
Serial.println(WiFi.localIP());
// Start streaming web server
startCameraServer();
server.on("/readRSSI", handleRSSI);
server.on("/readLED", handleLED); //To get update of ADC Value only
server.begin(); //Start server
Serial.println("HTTP server started");
ledcWrite(Flashlight, 0x01);
}
void loop() {
server.handleClient();
delay(1);
}
The web page with Network traffic & errors:
Tried the code in both AP & Client Modes and its works, but with intermittant error 404, also due to properties of Ajax, it seem I should keep the socket as 80 for Webserver, and it may be another reason of crash ....
**
**
For simplicity, as usual, I ran all the code, and observed many network errors occured, next, for simplicity, I keeped the Web page being run and I reprogramed the ESP32 without the startCameraServer() function of the official http server, then I found no error at all for the xhr requests/ returns for every interval of 200ms.
So it seem esp_http_server and WebServer libraries have some collision there ....
After some efforts, it make simplier to me to use one http server libary for all the video streaming, handling http requests and response ajax requests. Finally ESPAsycnWebSvr library was adopted. A sample code from GitHub of this library was modified (https://gist.github.com/me-no-dev)
Now, the ESP32-CAM (AI Thinker) provides 2 ways communication with Camera streaming. Both AP mode and Client mode was tested.
Test Result: Network Performance
The code is shared for those who need this functions:
#include <WiFi.h>
#include "Arduino.h"
#include "esp_camera.h"
#include "ESPAsyncWebSrv.h"
#include "soc/soc.h" // disable brownout problems
#include "soc/rtc_cntl_reg.h" // disable brownout problems
#define Flashlight 4 // Build-in flashlight of ESP32-CAM Board
const int ledFreq = 1000;
const int ledChannel = 4;
const int ledResolution = 12; // 12 bits give good resolution at low lux
int led_intensity = 0;
// For AI Thinker ESP32-CAM Board ONLY
#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
// WiFi credentials
const char* ssid = "SSID";
const char* password = "Password";
typedef struct {
camera_fb_t * fb;
size_t index;
} camera_frame_t;
#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: %s\r\nContent-Length: %u\r\n\r\n";
static const char * JPG_CONTENT_TYPE = "image/jpeg";
static const char * BMP_CONTENT_TYPE = "image/x-windows-bmp";
AsyncWebServer server(80);
const char* htmlHomePage PROGMEM = R"HTMLHOMEPAGE(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<style>
.slider {
width: 20%;
height: 10px;
border-radius: 3px;
}
</style>
</head>
<body class="noselect" align="center" style="background-color:white">
<h1><br></h1>
<img id="cameraImage" src="" style="width:400px;height:250px">
<h2><br></h2>
<div class="slidecontainer">LED_Brightness
<input type="range" min="0" max="255" value="0" class="slider" id="LEDslider" oninput='sendButtonInput("LED",value)'>
</div>
<div class="LED_feedback_value">
<h4>The feedback of LED intensity: <span id="LED_feedback_value">0</span></h4>
</div>
<div class="RSSI">
<h4>The Received Signal Strength (RSSI): <span id="RSSI_feedback_value">N/A </span>dBm</h4><br>
</div>
<script>
var counter1 = 0;
let panelButtons = 0;
function controlPanel(){
fetch(document.location.origin+'/control?var=led_PWM&val='+ document.getElementById("LEDslider").value);
}
function getLEDval(){
var xhttpr = new XMLHttpRequest();
xhttpr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
document.getElementById("LED_feedback_value").innerHTML = this.responseText;
}
}
xhttpr.open("GET", "readLED", true);
xhttpr.send();
}
function getRSSI(){
var xhttpr = new XMLHttpRequest();
xhttpr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
document.getElementById("RSSI_feedback_value").innerHTML = this.responseText;
}
}
xhttpr.open("GET", "readRSSI", true);
xhttpr.send();
}
setInterval(function(){ // pressure test, every 200ms
controlPanel();
counter1+=1;
if (counter1==2){
getLEDval();
}
if (counter1==4){
counter1=0;
getRSSI();
}
},200);
window.onload = document.getElementById("cameraImage").src = window.location + "stream";
</script>
</body>
</html>
)HTMLHOMEPAGE";
class AsyncBufferResponse: public AsyncAbstractResponse {
private:
uint8_t * _buf;
size_t _len;
size_t _index;
public:
AsyncBufferResponse(uint8_t * buf, size_t len, const char * contentType){
_buf = buf;
_len = len;
_callback = nullptr;
_code = 200;
_contentLength = _len;
_contentType = contentType;
_index = 0;
}
~AsyncBufferResponse(){
if(_buf != nullptr){
free(_buf);
}
}
bool _sourceValid() const { return _buf != nullptr; }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override{
size_t ret = _content(buf, maxLen, _index);
if(ret != RESPONSE_TRY_AGAIN){
_index += ret;
}
return ret;
}
size_t _content(uint8_t *buffer, size_t maxLen, size_t index){
memcpy(buffer, _buf+index, maxLen);
if((index+maxLen) == _len){
free(_buf);
_buf = nullptr;
}
return maxLen;
}
};
class AsyncFrameResponse: public AsyncAbstractResponse {
private:
camera_fb_t * fb;
size_t _index;
public:
AsyncFrameResponse(camera_fb_t * frame, const char * contentType){
_callback = nullptr;
_code = 200;
_contentLength = frame->len;
_contentType = contentType;
_index = 0;
fb = frame;
}
~AsyncFrameResponse(){
if(fb != nullptr){
esp_camera_fb_return(fb);
}
}
bool _sourceValid() const { return fb != nullptr; }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override{
size_t ret = _content(buf, maxLen, _index);
if(ret != RESPONSE_TRY_AGAIN){
_index += ret;
}
return ret;
}
size_t _content(uint8_t *buffer, size_t maxLen, size_t index){
memcpy(buffer, fb->buf+index, maxLen);
if((index+maxLen) == fb->len){
esp_camera_fb_return(fb);
fb = nullptr;
}
return maxLen;
}
};
class AsyncJpegStreamResponse: public AsyncAbstractResponse {
private:
camera_frame_t _frame;
size_t _index;
size_t _jpg_buf_len;
uint8_t * _jpg_buf;
uint64_t lastAsyncRequest;
public:
AsyncJpegStreamResponse(){
_callback = nullptr;
_code = 200;
_contentLength = 0;
_contentType = STREAM_CONTENT_TYPE;
_sendContentLength = false;
_chunked = true;
_index = 0;
_jpg_buf_len = 0;
_jpg_buf = NULL;
lastAsyncRequest = 0;
memset(&_frame, 0, sizeof(camera_frame_t));
}
~AsyncJpegStreamResponse(){
if(_frame.fb){
if(_frame.fb->format != PIXFORMAT_JPEG){
free(_jpg_buf);
}
esp_camera_fb_return(_frame.fb);
}
}
bool _sourceValid() const {
return true;
}
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override {
size_t ret = _content(buf, maxLen, _index);
if(ret != RESPONSE_TRY_AGAIN){
_index += ret;
}
return ret;
}
size_t _content(uint8_t *buffer, size_t maxLen, size_t index){
if(!_frame.fb || _frame.index == _jpg_buf_len){
if(index && _frame.fb){
uint64_t end = (uint64_t)micros();
int fp = (end - lastAsyncRequest) / 1000;
// log_printf("Size: %uKB, Time: %.0fms (%.1ffps)\n", _jpg_buf_len/1024, fp, 1000./fp); // METER !
lastAsyncRequest = end;
if(_frame.fb->format != PIXFORMAT_JPEG){
free(_jpg_buf);
}
esp_camera_fb_return(_frame.fb);
_frame.fb = NULL;
_jpg_buf_len = 0;
_jpg_buf = NULL;
}
if(maxLen < (strlen(STREAM_BOUNDARY) + strlen(STREAM_PART) + strlen(JPG_CONTENT_TYPE) + 8)){
//log_w("Not enough space for headers");
return RESPONSE_TRY_AGAIN;
}
//get frame
_frame.index = 0;
_frame.fb = esp_camera_fb_get();
if (_frame.fb == NULL) {
log_e("Camera frame failed");
return 0;
}
if(_frame.fb->format != PIXFORMAT_JPEG){
unsigned long st = millis();
bool jpeg_converted = frame2jpg(_frame.fb, 80, &_jpg_buf, &_jpg_buf_len);
if(!jpeg_converted){
log_e("JPEG compression failed");
esp_camera_fb_return(_frame.fb);
_frame.fb = NULL;
_jpg_buf_len = 0;
_jpg_buf = NULL;
return 0;
}
// log_i("JPEG: %lums, %uB", millis() - st, _jpg_buf_len); // METER !!
} else {
_jpg_buf_len = _frame.fb->len;
_jpg_buf = _frame.fb->buf;
}
//send boundary
size_t blen = 0;
if(index){
blen = strlen(STREAM_BOUNDARY);
memcpy(buffer, STREAM_BOUNDARY, blen);
buffer += blen;
}
//send header
size_t hlen = sprintf((char *)buffer, STREAM_PART, JPG_CONTENT_TYPE, _jpg_buf_len);
buffer += hlen;
//send frame
hlen = maxLen - hlen - blen;
if(hlen > _jpg_buf_len){
maxLen -= hlen - _jpg_buf_len;
hlen = _jpg_buf_len;
}
memcpy(buffer, _jpg_buf, hlen);
_frame.index += hlen;
return maxLen;
}
size_t available = _jpg_buf_len - _frame.index;
if(maxLen > available){
maxLen = available;
}
memcpy(buffer, _jpg_buf+_frame.index, maxLen);
_frame.index += maxLen;
return maxLen;
}
};
void handleRoot(AsyncWebServerRequest *request)
{
request->send_P(200, "text/html", htmlHomePage);
}
void handleNotFound(AsyncWebServerRequest *request)
{
request->send(404, "text/plain", "ESP32: File Not Found");
}
void streamJpg(AsyncWebServerRequest *request){
AsyncJpegStreamResponse *response = new AsyncJpegStreamResponse();
if(!response){
request->send(501);
return;
}
response->addHeader("Access-Control-Allow-Origin", "*");
request->send(response);
}
void handleControl(AsyncWebServerRequest *request){
if(!request->hasArg("var") || !request->hasArg("val")){
request->send(404);
return;
}
String var = request->arg("var");
const char * variable = var.c_str();
int val = atoi(request->arg("val").c_str());
sensor_t * s = esp_camera_sensor_get();
if(s == NULL){
request->send(501);
return;
}
if(!strcmp(variable, "led_PWM")){
led_intensity = val/4;
// Serial.printf("Command: LED PWM = %u\n",led_intensity); // This line would slow down the ESP32
ledcWrite(Flashlight, led_intensity); // val
}
else {
log_e("unknown command %s", var.c_str());
request->send(404);
return;
}
// log_d("Got setting %s with value %d. Res: %d", var.c_str(), val, res);
AsyncWebServerResponse * response = request->beginResponse(200);
response->addHeader("Access-Control-Allow-Origin", "*");
request->send(response);
}
void setup(){
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Disable brownout detector
ledcSetup(ledChannel, ledFreq, ledResolution); // Setup LED PWM freq & resolution
ledcAttachPin(Flashlight, 4); // Channel 4
ledcWrite(Flashlight, 0x03); // Startup indicated by low light
Serial.begin(115200);
Serial.setDebugOutput(true);
// Start Wifi and init camera //
// Start Wifi
IPAddress local_ip(192,168,4,1);
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);
WiFi.softAPConfig(local_ip, gateway, subnet); //AP Mode
WiFi.softAP(ssid, password); //
Serial.print("AP IP address: "); //
Serial.println(WiFi.softAPIP()); //
WiFi.begin(ssid, password); // Client Mode
WiFi.setSleep(false); //
while (WiFi.status() != WL_CONNECTED) { //
delay(500); //
Serial.print("."); //
} //
Serial.println(""); //
Serial.println("WiFi connected"); //
Serial.print("Camera Stream Ready! Go to: http://"); //
Serial.println(WiFi.localIP()); //
// init camera
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.pixel_format = PIXFORMAT_JPEG;
if(psramFound()){
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
log_e("ERROR : Camera init failed with error 0x%x\n", err);
return;
}
else {
log_d("Camera init OK");
}
sensor_t * s = esp_camera_sensor_get();
log_d("Sensor PID : %d\n",s->id.PID);
// =============================================
server.on("/", HTTP_GET, handleRoot);
server.on("/stream", HTTP_GET, streamJpg);
server.on("/control", HTTP_GET, handleControl);
server.onNotFound(handleNotFound);
// Ajax xhr response to Web page
server.on("/readLED", HTTP_GET, [](AsyncWebServerRequest *req){
req->send(200, "text/plain",String(led_intensity));
});
server.on("/readRSSI", HTTP_GET, [](AsyncWebServerRequest *req){
req->send(200, "text/plain",String(WiFi.RSSI()));
});
server.begin();
}
// ==== UPDATE : main loop ====
void loop(){
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
// ============================