Remote Video Control Car via HTTP Server on Mobile – ESP32-CAM IoT Project

Remote Video Control Car via HTTP Server on Mobile - ESP32-CAM IoT Project

In this article, we will implement the following functionality:

The mobile client, written in JavaScript, sends control signals to an HTTP server software written in C#, which then forwards the control signals to the video car. Upon receiving the control signals, the video car performs the corresponding actions.

Simultaneously, the video car sends the captured video stream to the server via the ESP32-CAM module, and the mobile remote control client pulls the video from the server for display, achieving the functionality of a remote video-controlled car.

This version, V1.1, will not be continued because using this method requires calling the mobile microphone permissions for voice communication, which must use an HTTPS connection. Handling HTTPS with the HTTP server in this version is quite cumbersome. Therefore, the main purpose of this article is to document this implementation method and some technical principles used in this approach for future learning and reference.

The ESP32-CAM Module Used in This Article

Remote Video Control Car via HTTP Server on Mobile - ESP32-CAM IoT Project

The Development Platform Chosen for This Article

The development platforms chosen for the program code involved in this article are Arduino and SharpDevelop 5.0.

Source Code for the Mobile Remote Control Client – index.html

<html><head><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no' ><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><script id='setvjs' type='text/javascript' src=''></script><style>body{  font-size:24px;  color:#FFFFFF;  transform:rotate(90deg);  }.touch-effect {  transition: transform 0.3s;}.touched {  transform:scale(0.85); /* Touch scaling effect */  opacity:0.6;          /* Touch opacity change */}.up{  transform:rotate(90deg);  position:absolute;  left:70vw;  top:60vh;  width:8vh;  height:30vw;}.down{  transform:rotate(90deg);  position:absolute;  left:20vw;  top:60vh;  width:8vh;  height:30vw;}.left{  transform:rotate(90deg);  position:absolute;  left:38vw;  top:50vh;  width:30vw;  height:10vh;}.right{  transform:rotate(90deg);  position:absolute;  left:38vw;  top:75vh;  width:30vw;  height:10vh;}.show_msg_div{  transform:rotate(90deg);  position:absolute;  left:60vw;  top:10vh;  width:8em;  height:8em;    background-color:rgba(0,0,0,0.5);}.video{  transform:rotate(90deg);  position:absolute;  left:-48vw;  top:19vh;  height:100vw;  width:100vh;   }</style><title>Universal Remote Control Program</title></head><!-- body  overall ontouchstart='return false' prevents the page from moving when touched --><body bgcolor='#000000' oncontextmenu='return false' onselectstart='return false' ontouchstart='return false'><img id='videox' src='/stream$imei$.stream' class='video' oncontextmenu='return false' onselectstart='return false'><div class='show_msg_div'><font color='#00FFFF'><b>Current Speed:</b><span id='pa_a_span'></span></font><br><font color='#FF00FF'><b>Current Direction:</b><span id='pa_b_span'></span></font><br></div><img src='up.png' class='up' ontouchstart='cmd_ontouchstart(event,1);'   ontouchend="cmd_ontouchend(event,1);"><img src='down.png' class='down' ontouchstart ='cmd_ontouchstart(event,2);'   ontouchend="cmd_ontouchend(event,2);">
<img src='left.png' class='left' ontouchstart ='cmd_ontouchstart(event,3);'   ontouchend="cmd_ontouchend(event,3);"><img src='right.png' class='right' ontouchstart ='cmd_ontouchstart(event,4);'    ontouchend="cmd_ontouchend(event,4);">
</body></html> <script>const imei='$imei$';const video=document.getElementById('video');const pa_a_span=document.getElementById('pa_a_span');const pa_b_span=document.getElementById('pa_b_span');const setvjs=document.getElementById('setvjs');//alert(imei);var myTimer=setInterval(myTimerFun,50);
var isSendok=true;var isup=false;var isdown=false;var isleft=false;var isright=false;
var speed=50;var direction=50;function cmd_ontouchstart(e,n){  log('cmd_ontouchstart');  const img = e.target;  img.classList.add('touched');    //Prevent default behavior (like long press menu)  e.preventDefault();    // Get touch point information  //const touch = e.touches[0];  //console.log(`Touch started at coordinates: (${touch.clientX}, ${touch.clientY})`);  if(n==1)  {    isup=true;  }  if(n==2)  {    isdown=true;  }  if(n==3)  {    isleft=true;  }  if(n==4)  {    isright=true;  }}function cmd_ontouchend(e,n){  //alert('cmd_ontouchend');  const img = e.target;    img.classList.remove('touched');  if(n==1)  {    isup=false;  }  if(n==2)  {    isdown=false;  }  if(n==3)  {    isleft=false;  }  if(n==4)  {    isright=false;  }}function myTimerFun(){    if(!isSendok)  {    return;//Need to get server feedback before sending the next control command  }  isSendok=false;  if(isup)  {    speed=speed+5;  }  if(isdown)  {    speed=speed-5;  }  if(isleft)  {    direction=direction-5;  }  if(isright)  {    direction=direction+5;  }  if(!isleft&amp;&amp;!isright)//This implements a slow automatic return to the straight direction, controlling the direction parameter to be between 10-90, 50 being straight ahead  {    if(direction>54)    {      direction=direction-5;    }    if(direction<46)    {      direction=direction+5;    }  }  if(speed>90)  {    speed=90;  }  if(speed<10)  {    speed=10;  }  if(direction>90)  {    direction=90;  }  if(direction<10)  {    direction=10;  }  speedold=speed;  directionold=direction;  //The control parameter range of 10-90 is because it takes two digits, here it is easy to combine speed and direction into a four-digit control command to send to the server  cmd=speed*100+direction;  loadScript('/setv.js?imei='+imei+'&amp;cmd='+cmd,setv_com);  //direction=0;  pa_a_span.innerText=speed;  pa_b_span.innerText=direction;}function setv_com(){  //This function is to send control commands from the mobile side to the server side, and can retrieve some data sent by the car to the server, such as battery level, signal strength, etc.  //log(isSendok);  //log(cmdreply);}
function loadScript(url,callback) {//Using dynamic loading of js files to submit control commands and retrieve some data sent by the car to the server, such as battery level, signal strength, etc.  var script=document.createElement('script');  script.type='text/javascript';  if (script.readyState)   { // IE    script.onreadystatechange = function() {      if (script.readyState === 'loaded' || script.readyState === 'complete') {        script.onreadystatechange = null;        callback();      }    };  } else { // Others    script.onload = function() {      callback();    };  }  script.src = url;  document.getElementsByTagName('head')[0].appendChild(script);}
// Use function to load script/*
loadScript('path/to/your/script.js', function() {  console.log('Script loaded and executed.');});
*/function log(v){  console.log(v);}</script>

The main new knowledge learned from this program includes:

1. Using img.classList.add(‘touched’); to dynamically add CSS effects. Using img.classList.remove(‘touched’); to dynamically remove CSS effects. Achieving touch transparency effects.

2. The overall use of ontouchstart=’return false’ in the body prevents the page from moving when touched. In previous versions, the phone screen would shake during the car’s movement, which was particularly inconvenient. Although this ontouchstart=’return false’ is always used, it was unexpected that the touch function was first disabled for the entire body and then enabled for the elements that needed touch functionality.

Remote Video Control Car via HTTP Server on Mobile - ESP32-CAM IoT Project

3. The functionality of dynamically loading JS files can achieve similar asynchronous operations for passing and retrieving parameters, each with its own advantages and disadvantages, and can be considered a method of implementation.

Remote Video Control Car via HTTP Server on Mobile - ESP32-CAM IoT Project

Source Code for the C# Server Program

TCPServer Class: This class mainly implements the functionality of starting the TCP service, establishing a long connection between the ESP32 and the server, and handling the retrieval of control commands and video stream transmission.

using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Collections.Concurrent;using System.Collections.Generic;namespace RemoteControlServer{  public class TCPServer  {//==============================================================int Port;TcpListener tcpListener;string temp;public TCPServer(int _Port){  Port=_Port;  tcpListener = new TcpListener(IPAddress.Any,Port);  tcpListener.Start();  tcpListener.BeginAcceptTcpClient(AcceptClientCallback, null);    S.log("TCP server started successfully, listening on port:"+Port);}static string GenerateClientId(){  return Guid.NewGuid().ToString("N").Substring(0, 8);}// Client connection callbackvoid AcceptClientCallback(IAsyncResult ar){  ClientInfo cinfo=new ClientInfo();    cinfo.client = tcpListener.EndAcceptTcpClient(ar);    IPEndPoint clientEndPoint = (IPEndPoint)cinfo.client.Client.RemoteEndPoint;    cinfo.ip = clientEndPoint.Address.ToString(); // Client IP    cinfo.port = clientEndPoint.Port;                // Client port    cinfo.client.ReceiveBufferSize=65536;    cinfo.client.Client.ReceiveBufferSize=65536;    cinfo.Stream= cinfo.client.GetStream();    cinfo.id=GenerateClientId();       S.log(cinfo.ip+" connected");    TCPServer_init(cinfo);//Must have a reply, otherwise the web side will be stuck          // Continue listening for new connections     tcpListener.BeginAcceptTcpClient(AcceptClientCallback, null);}void TCPServer_init(ClientInfo cinfo){    temp="HTTP/1.1 200 OK\r\n";  S.log(temp);  byte[] idData = Encoding.UTF8.GetBytes(temp);    cinfo.Stream.BeginWrite(idData, 0, idData.Length,TCPServer_initCallback,cinfo);}void TCPServer_initCallback(IAsyncResult ar){    ClientInfo cinfo=(ClientInfo)ar.AsyncState;    try    {       cinfo.Stream.EndWrite(ar);                S.clients.TryAdd(cinfo.id,cinfo);        S.onlineN=S.onlineN+1;        //setTitle();         // Start receiving data        cinfo.ReadBuffer=new byte[65536];        cinfo.Stream.BeginRead(cinfo.ReadBuffer,0, cinfo.ReadBuffer.Length, ReadCallback, cinfo);    }    catch(Exception e)    {      //debug(e.Message);        CleanupClient(cinfo.id);    }}// Data reception callbackvoid ReadCallback(IAsyncResult ar){    ClientInfo cinfo=(ClientInfo)ar.AsyncState;    //try    {        int bytesRead = cinfo.Stream.EndRead(ar);        if (bytesRead<1)        {          CleanupClient(cinfo.id);          return;        }        string message = Encoding.UTF8.GetString(cinfo.ReadBuffer,0, bytesRead);        int maxlen=message.Length>200?200:message.Length;        S.log("Received message from "+cinfo.id+": "+message.Substring(0,maxlen));    S.log("Message length"+bytesRead);                         if(message.IndexOf("HTTP")>-1)        {          //Browser side access to the server will definitely carry an HTTP, so use this as a feature to enable HTTP service functionality          //HTTPServer(cinfo,message);        }        if(message.IndexOf("imei")>-1)        {          //When using the Yinda DTU module Air780E to connect to the internet, the device identification string is automatically reported          //{"iccid":"898608491924D5103396","fver":"YED_DTU2_1.0.8","imei":"863644076551796","csq":31}          S.log("------------"+message);          string imei=S.Split(message,"\"imei\":\"")[1];                     imei=S.Split(imei,"\"")[0];          imei=imei.Replace("\r\n","\");//Remember special characters like \r\n, it took two hours          ClientInfo tempInfo=new ClientInfo();          bool aaa=false ;          aaa=S.clients.TryGetValue(cinfo.id, out tempInfo);          //S.log(aaa);          aaa=S.clients.TryRemove(cinfo.id,out tempInfo);          //S.log(aaa);          tempInfo.id=imei;      aaa=S.clients.TryAdd(imei,tempInfo);      //S.log(aaa);        S.log("Client "+cinfo.id+" connected, currently online:"+S.clients.Count);                    foreach( string key in S.clients.Keys)        {          //S.log(key);          //S.log(S.clients[key].ip);          //S.log(S.clients[key].port);        }       TCPServer_Send(cinfo,"videook");                }        //if(message.IndexOf("jpeg")>-1)//Video data        if(message.IndexOf("okmejpegstart")>-1)        {                        lock(cinfo._frameLock)          {              //cinfo._latestFrame = cinfo.ReadBuffer;              S.log("Receiving new data frame------");               Array.Resize(ref cinfo._latestFrame,bytesRead);              Buffer.BlockCopy(cinfo.ReadBuffer,13, cinfo._latestFrame, 0, bytesRead-13);              cinfo._lastUpdate = DateTime.Now;                          S.log("Receiving new data frame, length:"+cinfo._latestFrame.Length);           }                    TCPServer_Send(cinfo,"videook");        }        // Continue listening for data       //cinfo.Stream.BeginRead(cinfo.ReadBuffer, 0,cinfo.ReadBuffer.Length, ReadCallback, cinfo);            }    //catch    {       // CleanupClient(cinfo.id);    }}  //  Send datavoid TCPServer_Send(ClientInfo cinfo,string msg){  S.log(msg);  byte[] idData = Encoding.UTF8.GetBytes(msg);    cinfo.Stream.BeginWrite(idData, 0, idData.Length,SendCallback,cinfo);}// Send completion callbackvoid SendCallback(IAsyncResult ar){    ClientInfo cinfo=(ClientInfo)ar.AsyncState;    try    {       cinfo.Stream.EndWrite(ar);        // Start receiving data        cinfo.Stream.BeginRead(cinfo.ReadBuffer,0, cinfo.ReadBuffer.Length, ReadCallback, cinfo);    }    catch(Exception e)    {      //debug(e.Message);        //CleanupClient(cinfo.id);    }}// Clean up clientvoid CleanupClient(string clientId){  ClientInfo cinfo;    if (S.clients.TryRemove(clientId, out cinfo))    {      try{      cinfo.Stream.Close();        //cinfo.WriteStream.Close();      }catch {}        //setTitle();    }}//===============================================================  }}

HTTPServer Class: This class mainly starts the HTTP service functionality of the server, enabling interaction between the mobile device and the server.

using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Collections.Concurrent;using System.Collections.Generic;namespace RemoteControlServer{  public class HTTPServer  {//==========================================================int Port;HttpListener httpListener;//string temp;  Because it does not use multithreading, cannot use public HTTPServer(int _Port){  Port=_Port;  httpListener = new HttpListener();    httpListener.Prefixes.Add("https://*:"+Port+"/");//Note that administrator privileges are required    httpListener.Start();        S.log("Web server started, listening on port:"+Port);    httpListener.BeginGetContext(HandleRequest, null);  }void HandleRequest(IAsyncResult result){    try    {        // Get request context        HttpListenerContext context =httpListener.EndGetContext(result);        // Continue listening for the next request (keep looping)        httpListener.BeginGetContext(HandleRequest, null);        // Use thread pool to handle current request (to avoid blocking the listening loop)        //ThreadPool.QueueUserWorkItem(ProcessRequest,context);        ProcessRequest(context);    }    catch (ObjectDisposedException)    {        // Listener has been closed, ignore exception    }    catch (Exception ex)    {        S.log("Error accepting request:"+ex);    }}void ProcessRequest(HttpListenerContext context){  // Handle CORS headers    HandleCorsHeaders(context);    try    {        HttpListenerRequest request = context.Request;        HttpListenerResponse response = context.Response;         response.Headers.Add("Connection", "close");      response.Headers.Add("Cache-Control","no-cache");          string path=request.Url.AbsolutePath;        S.log(path);        if(path.IndexOf(".html")>-1)        {          response.ContentType = "text/html; charset=utf-8";        string imei=request.QueryString["imei"];                if(path.IndexOf("index.html")>-1)        {          new Index(response,path,imei);        }        }        if(path.IndexOf(".png")>-1)        {          response.ContentType = "image/png";          new ResponseWriteFile(response,path);        }        if(path.IndexOf(".js")>-1)        {          response.ContentType = "application/javascript";          new ResponseWriteJs(request,response,path);        }                if(path.IndexOf(".stream")>-1)//Must start with a letter stream863644076551796.stream        {          HandleStream(request,response);        }        if(path.IndexOf("uploadvideo")>-1)        {               //S.log("uploadvideo");          HandleUpload(request,response);                  }        if(path.IndexOf("UploadAudio")>-1)        {               new UploadAudio(request,response,path);          return;        }        if(path.IndexOf("AudioStreamMobToESP")>-1)        {               new AudioStreamMobToESP(request,response);        }       response.StatusCode = 200;       response.Close();       if(request.IsWebSocketRequest)       {                }    }    catch (Exception ex)    {        context.Response.StatusCode = 500;        var error = Encoding.UTF8.GetBytes("Server Error: "+ex.Message);        context.Response.OutputStream.Write(error, 0, error.Length);        context.Response.Close();    }}void HandleStream(HttpListenerRequest request, HttpListenerResponse response){  string imei=request.Url.AbsolutePath;     imei=imei.Replace("/stream"," ");    imei=imei.Replace(".stream"," ");    ClientInfo cInfo=new ClientInfo();    if(!S.clients.TryGetValue(imei,out cInfo))    {             HTTPServer_Send(response,"No video data yet");    return;    }        response.ContentType = "multipart/x-mixed-replace;boundary=frame";    response.SendChunked = true;    response.Headers.Add("Connection", "keep-alive");    response.Headers.Add("Cache-Control", "no-cache");    var stream = response.OutputStream;    var initialBoundary = Encoding.ASCII.GetBytes("--frame\r\n");    var boundaryBytes = Encoding.ASCII.GetBytes("\r\n--frame\r\n");    var headerBytes = Encoding.ASCII.GetBytes("Content-Type:image/jpeg\r\n\r\n");              try    {        bool isfirst=true;        while (true)        {            byte[] frameCopy;            lock (cInfo._frameLock)            {         //S.log("copyframe ...");                                  frameCopy = (byte[])cInfo._latestFrame.Clone(); // Deep copy to avoid data competition            }            if (frameCopy.Length > 0)            {              if(isfirst)              {                //Send initial boundary                isfirst=false;                stream.Write(initialBoundary, 0, initialBoundary.Length);              }              else              {                  stream.Write(boundaryBytes, 0, boundaryBytes.Length);              }                stream.Write(headerBytes, 0, headerBytes.Length);                stream.Write(frameCopy, 0, frameCopy.Length);                stream.Flush();            }                            if ((DateTime.Now - cInfo._lastUpdate).TotalSeconds > 500)            {                S.log("xxx"+cInfo._lastUpdate);                Thread.Sleep(2000);            }            Thread.Sleep(40); // About 25 FPS        }    }    catch (Exception ex)    {        S.log("Client disconnected");        //S.log(ex.Message);        cInfo=null;        response.Close();        S.clients.TryRemove(imei,out cInfo);    }    finally    {        cInfo=null;        response.Close();        S.clients.TryRemove(imei,out cInfo);    }}// Send datavoid HTTPServer_Send(HttpListenerResponse response,string msg){  response.Headers.Add("Connection", "close");    response.Headers.Add("Cache-Control","no-cache");      response.ContentType = "text/html; charset=utf-8";    var stream = response.OutputStream;    byte[] buff=Encoding.UTF8.GetBytes(msg);    stream.Write(buff,0,buff.Length);    stream.Flush();}void HandleUpload(HttpListenerRequest request, HttpListenerResponse response){  //S.log("Received request from "+request.RemoteEndPoint);  //S.log("Content type:"+request.ContentType);  //S.log("Content length: "+request.ContentLength64+"bytes");      string imei=request.Headers.Get("imei");    ClientInfo cinfo;    if(!S.clients.TryGetValue(imei, out cinfo))    {      cinfo=new ClientInfo();      S.clients.TryAdd(imei,cinfo);    }        MemoryStream ms = new MemoryStream();    request.InputStream.CopyTo(ms);      lock (cinfo._frameLock)    {            cinfo._latestFrame = ms.ToArray();        cinfo._lastUpdate = DateTime.Now;    }  //S.log("Receiving new data frame");     response.StatusCode = 200;    string cmd;    int cmdN=0;    lock(cinfo.cmdLock)    {      cmd=cinfo.cmd;      cinfo.cmdN++;      cmdN=cinfo.cmdN;    }    if(cmdN>10)    {      cmd="5050";//Stop signal    }    var responseBytes = Encoding.UTF8.GetBytes(cmd);    response.OutputStream.Write(responseBytes, 0, responseBytes.Length);    response.Close();}void HandleCorsHeaders(HttpListenerContext context){    // Allowed domains (in production, specify specific domains)* means allow all    context.Response.Headers.Add("Access-Control-Allow-Origin", "*");        // Allowed HTTP methods    context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");        // Allowed request headers must be combined into one line with commas, otherwise the latter will overwrite the former    context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,Authorization,imei,Audio");     // Preflight request cache time (seconds)    //context.Response.Headers.Add("Access-Control-Max-Age", "86400");        // If you need to support credentials (like cookies), set this item (cannot be used with Allow-Origin:*)    // context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");}//=========================================================  }  }

Client Structure:

using System;using System.Net.Sockets;namespace RemoteControlServer{  public class ClientInfo  {    public string id{ get; set; }      public NetworkStream Stream { get; set; }      public byte[] ReadBuffer { get; set; }      public TcpClient client { get; set; }      public string ip{ get; set; }      public int port{ get; set; }            // Shared frame data      public byte[] _latestFrame = Array.Empty<byte>();      public readonly object _frameLock = new object();      public DateTime _lastUpdate = DateTime.MinValue;            //Voice===============================      ///Voice cache from mobile to ESP32      public byte[] mTeAudio = Array.Empty<byte>();      public readonly object mTeAudioLock = new object();            //=================================================      public string cmd="";      ///Reset the cmd received from the mobile side to 0, when greater than 10 it proves that the mobile side is not sending control signals,      /// so a stop signal can be sent to the car      public int cmdN=0;      public readonly object cmdLock = new object();      public string cmdreply="";      public readonly object cmdreplyLock = new object();        }}

Class for Handling HTML Files:

using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Collections.Concurrent;using System.Collections.Generic;namespace RemoteControlServer{  public class Html  {//===========================================================================HttpListenerRequest request;HttpListenerResponse response;string temp;string path;public Html(HttpListenerRequest _request, HttpListenerResponse _response){  request=_request;  response=_response;  response.Headers.Add("Connection", "close");    response.Headers.Add("Cache-Control","no-cache");      response.ContentType = "text/html; charset=utf-8";    var stream = response.OutputStream;    temp=File.ReadAllText(path.Replace("/",""));    temp=temp.Replace("$bm$",request.QueryString["bm"]);    //S.log();        byte[] buff=Encoding.UTF8.GetBytes(path);    stream.Write(buff,0,buff.Length);    stream.Flush();}//===========================================================================  }}

Class for Handling Dynamically Loaded JS Scripts for Asynchronous Parameter Passing:

using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Collections.Concurrent;using System.Collections.Generic;namespace RemoteControlServer{  public class ResponseWriteJs  {//===========================================================================public ResponseWriteJs(HttpListenerRequest request,HttpListenerResponse response,string path){  byte[] buff;  var stream = response.OutputStream;  if(path.IndexOf("setv.js")>-1)  {    string imei=request.QueryString["imei"];    string cmd=request.QueryString["cmd"];        ClientInfo cinfo;//Cannot directly =new here      if(!S.clients.TryGetValue(imei, out cinfo))      {        cinfo=new ClientInfo();        S.clients.TryAdd(imei,cinfo);      }        lock(cinfo.cmdLock)    {      cinfo.cmd=cmd;      cinfo.cmdN=0;    }      //S.log(cmd+"=============");    string cmdreply;    lock(cinfo.cmdreplyLock)    {      cmdreply=cinfo.cmdreply;    }      cmdreply=cmdreply+DateTime.Now;    string temp="cmdreply='"+cmdreply+"';isSendok=true;";    buff=Encoding.UTF8.GetBytes(temp);    stream = response.OutputStream;      stream.Write(buff,0,buff.Length);      stream.Flush();      response.Close();    return;  }    buff=File.ReadAllBytes(path.Replace("/",""));            stream.Write(buff,0,buff.Length);    stream.Flush();    response.Close();}//===========================================================================  }}

Source Code for the ESP32-CAM Program

#include <String.h>#include "esp_camera.h"#include <WiFi.h>#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM#define FRAMES_PER_SECOND 12#include "camera_pins.h"#include "HTTPClient.h"
#include "NeZha_I2C.h"#include "NeZha.h"#include "NeZha_Vehicle_Chassis.h"
camera_fb_t *fb = NULL;uint8_t *_jpg_buf;size_t _jpg_buf_len;HTTPClient http;String temp;
void Camera_init();void upload_frame();void WIFI_STA_INIT();void NeZha_Loop(unsigned int paNum);String localIP="";String WIFI_STA_MAC;unsigned int NeZha_v=0;unsigned int lfold=0;void setup() {  Serial.begin(115200);   Camera_init();  WIFI_STA_INIT();  Serial.print("Camera Ready! Use 'http://");  localIP=WiFi.localIP().toString();  Serial.print(localIP);  Serial.println("' to connect");
  //Nezha car    NeZha_Init();  Vehicle_Chassis_Init();}void loop() {  //Vehicle_Chassis_Forward(100);//0-1000  upload_frame();  //delay(2000);  if (Serial.available())    {      temp =Serial.readString();      //The string sent to the ESP32 via the serial port automatically adds a newline, so add \n here
             Serial.println("Executing command:")+temp+"\n");      if(temp=="init\n")      {                   }    }  //Serial.println(millis()); // delay(1000);}
void WIFI_STA_INIT(){  WiFi.disconnect();//First close existing WIFI connections  WiFi.mode(WIFI_STA);//STA  //AP mode IP configuration  // Configure fixed IP   Device IP, default gateway, subnet mask, preferred DNS, backup DNS  //if (!WiFi.config(IPAddress(192,168,1,188),IPAddress(192,168,1,1),IPAddress(255,255,255,0),IPAddress(192,168,1,1),IPAddress(192,168,1,1)))  //if (!WiFi.config(IPAddress(192,168,0,188),IPAddress(192,168,0,1),IPAddress(255,255,255,0),IPAddress(192,168,0,1),IPAddress(192,168,0,1)))   {   // Serial.println("STA Failed to configure");  } 
  //WiFi.begin(ssid, password);//Your WIFI name and password  WiFi.setSleep(false);  while (WiFi.status() != WL_CONNECTED) {    delay(500);    Serial.print("Connecting WIFI ......");  }  localIP=WiFi.localIP().toString();  WIFI_STA_MAC=WiFi.macAddress();  WIFI_STA_MAC.replace(":","");  Serial.println("WIFI_STA_MAC="+WIFI_STA_MAC);  Serial.println("WiFi connected");}void upload_frame() {   fb = esp_camera_fb_get();  if(!fb) {    Serial.println("Camera capture failed");//Note to configure the hardware in the development environment     return;  }
 // Convert to JPEG  if(!frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len)){    Serial.println("JPEG conversion failed");    esp_camera_fb_return(fb);    return;  }
  //http.setReuse(false); // Disable connection reuse  http.setTimeout(5000); // Set 5 seconds timeout
  if (!http.begin("http://192.168.1.103:8080/uploadvideo")) {  //if (!http.begin("http://www.okmedo.com:8080/uploadvideo")) {    Serial.println("HTTP initialization failed!");    return;  }
  http.addHeader("imei",WIFI_STA_MAC);  http.addHeader("Content-Type", "image/jpeg");    int httpResponseCode = http.POST(_jpg_buf,_jpg_buf_len);  if(httpResponseCode != 200)  {    Serial.printf("Upload failed, error code:%d\n", httpResponseCode);  }  else  {    unsigned int paNum=http.getString().toInt();    Serial.println(paNum);    NeZha_Loop(paNum);  }  if(httpResponseCode <= 0) {    Serial.printf("HTTP error code: %d\n", httpResponseCode);    Serial.printf("Internal error message: %s\n", http.errorToString(httpResponseCode).c_str());    Serial.printf("Response header: %s\n", http.header("Server").c_str());  }    http.end();  // Clean up resources  esp_camera_fb_return(fb);  free(_jpg_buf);  fb = NULL;  _jpg_buf = NULL;  }
void NeZha_Loop(unsigned int paNum){  unsigned int fb=paNum/100;  unsigned int lf=paNum%100;  unsigned int mapNum=0;  char Led_State=0;  Serial.println("fb="+String(fb)+"------lf="+String(lf));//10-90    mapNum=map(fb,10,90,0,2000);  if(mapNum>1100)  {    Vehicle_Chassis_Forward(mapNum-1100);//0-1000  }  if(mapNum<900)  {    Vehicle_Chassis_Backward(900-mapNum);    Led_State=1;  }
    mapNum=map(lf,10,90,110,180);    arc_servo.pwm = mapNum;    Arc_ServoPwm_Set(arc_servo.pwm);
    if(lf<50)    {      //Vehicle_Chassis_TurnLeft(100);      //Arc_ServoPwm_TurnLeft(6);      Led_State=2;    }    if(lf>50)    {          //Vehicle_Chassis_TurnRight(100);      //Arc_ServoPwm_TurnRight(6);      Led_State=3;    }    //Tail light control; 0: off, 1: reverse, 2: left turn light, 3: right turn light  switch(Led_State)  {    case 0:{    NeZha_TailLeftLed_TurnOff();    NeZha_TailRightLed_TurnOff();    }break;    case 1:{    NeZha_TailLeftLed_TurnOn();    NeZha_TailRightLed_TurnOn();    }break;    case 2:{    NeZha_TailLeftLed_Turn();    NeZha_TailRightLed_TurnOff();    }break;         case 3:{    NeZha_TailLeftLed_TurnOff();    NeZha_TailRightLed_Turn();    }break;      default:{    NeZha_TailLeftLed_TurnOff();    NeZha_TailRightLed_TurnOff();    }break;      }}
//Camera initializationvoid Camera_init(){  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_sccb_sda = SIOD_GPIO_NUM;  config.pin_sccb_scl = SIOC_GPIO_NUM;  config.pin_pwdn = PWDN_GPIO_NUM;  config.pin_reset = RESET_GPIO_NUM;  config.xclk_freq_hz = 15000000;  /* @param frame_size   One of    *                     - FRAMESIZE_96X96,    // 96x96    *                     - FRAMESIZE_QQVGA,    // 160x120    *                     - FRAMESIZE_QCIF,     // 176x144    *                     - FRAMESIZE_HQVGA,    // 240x176    *                     - FRAMESIZE_240X240,  // 240x240    *                     - FRAMESIZE_QVGA,     // 320x240    *                     - FRAMESIZE_CIF,      // 400x296    *                     - FRAMESIZE_HVGA,     // 480x320    *                     - FRAMESIZE_VGA,      // 640x480    *                     - FRAMESIZE_SVGA,     // 800x600    *                     - FRAMESIZE_XGA,      // 1024x768    *                     - FRAMESIZE_HD,       // 1280x720    *                     - FRAMESIZE_SXGA,     // 1280x1024    *                     - FRAMESIZE_UXGA,     // 1600x1200    *                     - FRAMESIZE_FHD,      // 1920x1080    *                     - FRAMESIZE_P_HD,     //  720x1280    *                     - FRAMESIZE_P_3MP,    //  864x1536    *                     - FRAMESIZE_QXGA,     // 2048x1536    *                     - FRAMESIZE_QHD,      // 2560x1440    *                     - FRAMESIZE_WQXGA,    // 2560x1600    *                     - FRAMESIZE_P_FHD,    // 1080x1920    *                     - FRAMESIZE_QSXGA,    // 2560x1920    */  config.frame_size = FRAMESIZE_QQVGA;//  config.pixel_format = PIXFORMAT_RGB565; // for streaming  PIXFORMAT_RGB565  //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 = 6;//12  config.fb_count = 1;
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality  //                      for larger pre-allocated frame buffer.  if(config.pixel_format == PIXFORMAT_JPEG){    if(psramFound()){      config.jpeg_quality = 10;      config.fb_count = 2;      config.grab_mode = CAMERA_GRAB_LATEST;    } else {      // Limit the frame size when PSRAM is not available      config.frame_size = FRAMESIZE_VGA;      config.fb_location = CAMERA_FB_IN_DRAM;    }  } else {    // Best option for face detection/recognition    // config.frame_size = FRAMESIZE_240X240;  #if CONFIG_IDF_TARGET_ESP32S3    config.fb_count = 2;  #endif  }
  #if defined(CAMERA_MODEL_ESP_EYE)  pinMode(13, INPUT_PULLUP);  pinMode(14, INPUT_PULLUP);  #endif
  // camera init  esp_err_t err = esp_camera_init(&amp;config);  if (err != ESP_OK) {    Serial.printf("Camera init failed with error 0x%x", err);    return;  }  Serial.println("Camera init ok");  sensor_t * s = esp_camera_sensor_get();}

Leave a Comment