#include "esp_camera.h"
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <iostream>
#include <sstream>
#include <ESP32Servo.h>
#define DUMMY_SERVO1_PIN 12
#define DUMMY_SERVO2_PIN 13
#define PAN_PIN 14
#define TILT_PIN 15
Servo dummyServo1;
Servo dummyServo2;
Servo panServo;
Servo tiltServo;
// Camera Pins (AI Thinker ESP32-CAM)
#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
const char* ssid = "NowISeeYou";
const char* password = "12345678";
AsyncWebServer server(80);
AsyncWebSocket wsCamera("/Camera");
AsyncWebSocket wsServoInput("/ServoInput");
uint32_t cameraClientId = 0;
#define LIGHT_PIN 4
const char* htmlHomePage PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body{
text-align:center;
background:white;
}
.slider{
width:300px;
}
img{
width:400px;
height:300px;
}
</style>
</head>
<body>
<h2>ESP32 CAM CONTROL</h2>
<img id="cameraImage" src="">
<br><br>
Pan:
<br>
<input type="range" min="0" max="180" value="90"
class="slider" id="Pan"
oninput='sendData("Pan",value)'>
<br><br>
Tilt:
<br>
<input type="range" min="0" max="180" value="90"
class="slider" id="Tilt"
oninput='sendData("Tilt",value)'>
<br><br>
Light:
<br>
<input type="range" min="0" max="255" value="0"
class="slider" id="Light"
oninput='sendData("Light",value)'>
<script>
var cameraSocket;
var controlSocket;
function initSockets()
{
cameraSocket = new WebSocket("ws://" + window.location.hostname + "/Camera");
cameraSocket.binaryType = "blob";
cameraSocket.onmessage = function(event)
{
document.getElementById("cameraImage").src =
URL.createObjectURL(event.data);
};
controlSocket = new WebSocket("ws://" + window.location.hostname + "/ServoInput");
}
function sendData(key,value)
{
controlSocket.send(key + "," + value);
}
window.onload = initSockets;
</script>
</body>
</html>
)rawliteral";
void handleRoot(AsyncWebServerRequest *request)
{
request->send_P(200, "text/html", htmlHomePage);
}
void handleNotFound(AsyncWebServerRequest *request)
{
request->send(404, "text/plain", "Not found");
}
void onServoInputWebSocketEvent(
AsyncWebSocket *server,
AsyncWebSocketClient *client,
AwsEventType type,
void *arg,
uint8_t *data,
size_t len)
{
switch(type)
{
case WS_EVT_CONNECT:
Serial.println("Servo Client Connected");
break;
case WS_EVT_DISCONNECT:
Serial.println("Servo Client Disconnected");
panServo.write(90);
tiltServo.write(90);
ledcWrite(LIGHT_PIN, 0);
break;
case WS_EVT_DATA:
AwsFrameInfo *info;
info = (AwsFrameInfo*)arg;
if(info->final &&
info->index == 0 &&
info->len == len &&
info->opcode == WS_TEXT)
{
std::string msg = "";
msg.assign((char*)data, len);
std::istringstream ss(msg);
std::string key;
std::string value;
getline(ss, key, ',');
getline(ss, value, ',');
int valueInt = atoi(value.c_str());
if(key == "Pan")
{
panServo.write(valueInt);
}
else if(key == "Tilt")
{
tiltServo.write(valueInt);
}
else if(key == "Light")
{
ledcWrite(LIGHT_PIN, valueInt);
}
}
break;
default:
break;
}
}
void onCameraWebSocketEvent(
AsyncWebSocket *server,
AsyncWebSocketClient *client,
AwsEventType type,
void *arg,
uint8_t *data,
size_t len)
{
switch(type)
{
case WS_EVT_CONNECT:
Serial.println("Camera Client Connected");
cameraClientId = client->id();
break;
case WS_EVT_DISCONNECT:
Serial.println("Camera Client Disconnected");
cameraClientId = 0;
break;
default:
break;
}
}
void setupCamera()
{
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;
// FIXED FOR ESP32 CORE 3.x
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 = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_VGA;
config.jpeg_quality = 10;
config.fb_count = 1;
esp_err_t err = esp_camera_init(&config);
if(err != ESP_OK)
{
Serial.printf("Camera Init Failed: 0x%x\n", err);
return;
}
Serial.println("Camera Init Success");
}
void sendCameraPicture()
{
if(cameraClientId == 0)
{
return;
}
camera_fb_t * fb = esp_camera_fb_get();
if(!fb)
{
Serial.println("Camera Capture Failed");
return;
}
wsCamera.binary(cameraClientId, fb->buf, fb->len);
esp_camera_fb_return(fb);
while(true)
{
AsyncWebSocketClient *client = wsCamera.client(cameraClientId);
if(!client || !client->queueIsFull())
{
break;
}
delay(1);
}
}
void setUpPinModes()
{
dummyServo1.attach(DUMMY_SERVO1_PIN);
dummyServo2.attach(DUMMY_SERVO2_PIN);
panServo.attach(PAN_PIN);
tiltServo.attach(TILT_PIN);
pinMode(LIGHT_PIN, OUTPUT);
// ESP32 CORE 3.x FIX
ledcAttach(LIGHT_PIN, 1000, 8);
}
void setup()
{
Serial.begin(115200);
setUpPinModes();
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP Address: ");
Serial.println(IP);
server.on("/", HTTP_GET, handleRoot);
server.onNotFound(handleNotFound);
wsCamera.onEvent(onCameraWebSocketEvent);
server.addHandler(&wsCamera);
wsServoInput.onEvent(onServoInputWebSocketEvent);
server.addHandler(&wsServoInput);
server.begin();
Serial.println("Server Started");
setupCamera();
}
void loop()
{
wsCamera.cleanupClients();
wsServoInput.cleanupClients();
sendCameraPicture();
}