ESP32-Cam PTZ Control, WiFi Config dan Video Streaming March 30, 2025 by
ESP32-Cam yang dapat Mengontrol 2Servo (PTZ atau Pan & Tilt), konfigurasi WiFi via webserver dan Streaming Video via web
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <ESP32Servo.h>
#include <EEPROM.h>
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.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"
// Pin definitions for ESP32-CAM
#define FLASH_GPIO_NUM 4
#define PAN_PIN 12 // Changed to GPIO12
#define TILT_PIN 2 // Changed to GPIO2
// EEPROM configuration
#define EEPROM_SIZE 512
#define EEPROM_START 0
// Stream content type
#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";
// Structure to store WiFi settings
typedef struct {
char sta_ssid[32];
char sta_password[64];
char ap_ssid[32];
char ap_password[64];
uint32_t sta_ip;
uint32_t sta_gateway;
uint32_t sta_subnet;
uint32_t ap_ip;
uint32_t ap_gateway;
uint32_t ap_subnet;
uint8_t mode; // 0 = STA, 1 = AP, 2 = STA+AP
} WiFiConfig;
// Camera configuration
#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
// Global variables
WebServer server(80);
WiFiConfig wifiConfig;
Servo panServo;
Servo tiltServo;
int panAngle = 90;
int tiltAngle = 90;
bool vertical_flip = false;
bool horizontal_flip = false;
httpd_handle_t stream_httpd = NULL;
// HTML content (same as in your original enhanced code)
const char* wifiConfigPage = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>ESP32-CAM WiFi Config</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 600px; margin: 0 auto; }
.tab { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; }
.tab button { background-color: inherit; float: left; border: none; outline: none; cursor: pointer; padding: 10px 16px; transition: 0.3s; }
.tab button:hover { background-color: #ddd; }
.tab button.active { background-color: #ccc; }
.tabcontent { display: none; padding: 12px; border: 1px solid #ccc; border-top: none; }
input, select { width: 100%; padding: 8px; margin: 5px 0 15px 0; display: inline-block; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #45a049; }
.status { margin-top: 20px; padding: 10px; background-color: #f8f8f8; border: 1px solid #ddd; }
#scanStatus { margin-left: 10px; color: #666; font-style: italic; }
</style>
</head>
<body>
<div class="container">
<h1>ESP32-CAM WiFi Configuration</h1>
<div class="status">
<h3>Current Status</h3>
%STATUS%
</div>
<div class="tab">
<button class="tablinks active" onclick="openTab(event, 'sta')">STA Mode</button>
<button class="tablinks" onclick="openTab(event, 'ap')">AP Mode</button>
<button class="tablinks" onclick="openTab(event, 'reboot')">System</button>
</div>
<div id="sta" class="tabcontent" style="display:block;">
<h2>STA (Client) Configuration</h2>
<form action="/save" method="post">
<input type="hidden" name="mode" value="0">
<label for="sta_ssid">WiFi Network:</label>
<input type="text" id="sta_ssid" name="sta_ssid" placeholder="Your ssid"><br>
<label for="sta_password">Password:</label>
<input type="password" id="sta_password" name="sta_password" placeholder="Leave empty if no password"><br>
<h3>Custom IP Settings (Optional)</h3>
<label for="sta_ip">Static IP:</label>
<input type="text" id="sta_ip" name="sta_ip" placeholder="e.g., 192.168.1.100"><br>
<label for="sta_gateway">Gateway:</label>
<input type="text" id="sta_gateway" name="sta_gateway" placeholder="e.g., 192.168.1.1"><br>
<label for="sta_subnet">Subnet Mask:</label>
<input type="text" id="sta_subnet" name="sta_subnet" placeholder="e.g., 255.255.255.0"><br>
<button type="submit">Save STA Configuration</button>
</form>
</div>
<div id="ap" class="tabcontent">
<h2>AP (Access Point) Configuration</h2>
<form action="/save" method="post">
<input type="hidden" name="mode" value="1">
<label for="ap_ssid">SSID:</label>
<input type="text" id="ap_ssid" name="ap_ssid" value="%AP_SSID%" required><br>
<label for="ap_password">Password (min 8 chars):</label>
<input type="password" id="ap_password" name="ap_password" value="%AP_PASSWORD%" minlength="8"><br>
<h3>IP Settings</h3>
<label for="ap_ip">IP Address:</label>
<input type="text" id="ap_ip" name="ap_ip" value="%AP_IP%" required><br>
<label for="ap_gateway">Gateway:</label>
<input type="text" id="ap_gateway" name="ap_gateway" value="%AP_GATEWAY%" required><br>
<label for="ap_subnet">Subnet Mask:</label>
<input type="text" id="ap_subnet" name="ap_subnet" value="%AP_SUBNET%" required><br>
<button type="submit">Save AP Configuration</button>
</form>
</div>
<div id="reboot" class="tabcontent">
<h2>System Operations</h2>
<form action="/reboot" method="post">
<button type="submit">Reboot Device</button>
</form>
</div>
</div>
<script>
function openTab(evt, tabName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(tabName).style.display = "block";
evt.currentTarget.className += " active";
}
function scanNetworks() {
const select = document.getElementById("sta_ssid");
const scanButton = document.getElementById("scanButton");
const scanStatus = document.getElementById("scanStatus");
select.innerHTML = '<option value="">Scanning...</option>';
scanButton.disabled = true;
scanStatus.textContent = "Scanning networks...";
fetch('/scan')
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
select.innerHTML = '<option value="">-- Select Network --</option>';
if (data.error) {
select.innerHTML = `<option value="">${data.error}</option>`;
scanStatus.textContent = data.error;
return;
}
if (data.length === 0) {
select.innerHTML = '<option value="">No networks found</option>';
scanStatus.textContent = "No networks found";
return;
}
data.forEach(network => {
const option = document.createElement("option");
option.value = network.ssid;
option.text = `${network.ssid} (${network.rssi} dBm${network.encryption ? ", Secured" : ", Open"})`;
if (network.ssid == "%STA_SSID%") option.selected = true;
select.appendChild(option);
});
scanStatus.textContent = `Found ${data.length} networks`;
})
.catch(error => {
console.error('Error:', error);
select.innerHTML = '<option value="">Scan failed</option>';
scanStatus.textContent = "Scan failed";
})
.finally(() => {
scanButton.disabled = false;
});
}
// Initial scan when page loads if on STA tab
window.onload = function() {
if (document.getElementById('sta').style.display === 'block') {
scanNetworks();
}
};
</script>
</body>
</html>
)rawliteral";
const char* mainPageContent = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial; text-align: center; margin: 0 auto; padding: 20px; }
.control { margin: 20px; }
.slider { width: 80%; margin: 10px; }
.button {
padding: 10px 20px;
font-size: 16px;
margin: 5px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
}
.button:hover { background-color: #45a049; }
.preset-btn { background-color: #008CBA; }
.preset-btn:hover { background-color: #0077a3; }
.video-container { margin: 20px 0; }
#video { max-width: 100%; height: auto; }
.tabs { overflow: hidden; border: 1px solid #ccc; background-color: #f1f1f1; }
.tablinks { background-color: inherit; float: left; border: none; outline: none; cursor: pointer;
padding: 10px 16px; transition: 0.3s; }
.tablinks:hover { background-color: #ddd; }
.tablinks.active { background-color: #ccc; }
.tabcontent { display: none; padding: 12px; border: 1px solid #ccc; border-top: none; }
</style>
</head>
<body>
<h1>ESP32-CAM PTZ Control</h1>
<div class="tabs">
<button class="tablinks active" onclick="openTab(event, 'ptz')">PTZ Control</button>
<button class="tablinks" onclick="openTab(event, 'video')">Video Stream</button>
<button class="tablinks" onclick="openTab(event, 'wifi')">WiFi Setup</button>
</div>
<div id="ptz" class="tabcontent" style="display:block;">
<div class="control">
<h2>Pan Control (Horizontal)</h2>
<input type="range" min="0" max="180" value="%PAN%" class="slider" id="panSlider" onchange="updatePan(this.value)">
<p>Angle: <span id="panValue">%PAN%</span>°</p>
<button class="button" onclick="movePan(-10)">-10°</button>
<button class="button" onclick="movePan(-5)">-5°</button>
<button class="button" onclick="movePan(5)">+5°</button>
<button class="button" onclick="movePan(10)">+10°</button>
<button class="button" onclick="centerPan()">Center (90°)</button>
</div>
<div class="control">
<h2>Tilt Control (Vertical)</h2>
<input type="range" min="0" max="180" value="%TILT%" class="slider" id="tiltSlider" onchange="updateTilt(this.value)">
<p>Angle: <span id="tiltValue">%TILT%</span>°</p>
<button class="button" onclick="moveTilt(-10)">-10°</button>
<button class="button" onclick="moveTilt(-5)">-5°</button>
<button class="button" onclick="moveTilt(5)">+5°</button>
<button class="button" onclick="moveTilt(10)">+10°</button>
<button class="button" onclick="centerTilt()">Center (90°)</button>
</div>
<div class="control">
<h2>Preset Positions</h2>
<button class="button preset-btn" onclick="setPreset(90, 90)">Center</button>
<button class="button preset-btn" onclick="setPreset(0, 45)">Left Down</button>
<button class="button preset-btn" onclick="setPreset(180, 45)">Right Down</button>
<button class="button preset-btn" onclick="setPreset(0, 135)">Left Up</button>
<button class="button preset-btn" onclick="setPreset(180, 135)">Right Up</button>
</div>
</div>
<div id="video" class="tabcontent">
<div class="video-container">
<img src= "/stream" id="videoStream" width="640" height="480">
</div>
<div class="controls">
<button class="button" onclick="rotateVertical()">Rotasi Vertikal</button>
<button class="button" onclick="rotateHorizontal()">Rotasi Horizontal</button>
<button class="button" onclick="resetRotation()">Reset Rotasi</button>
</div>
</div>
<div id="wifi" class="tabcontent">
<h2>WiFi Configuration</h2>
<p>Click the button below to configure WiFi settings</p>
<button class="button" onclick="window.location.href='/setup'">Go to WiFi Setup</button>
</div>
<script>
function updatePan(angle) {
document.getElementById("panValue").innerHTML = angle;
fetch('/pan?value=' + angle);
}
function updateTilt(angle) {
document.getElementById("tiltValue").innerHTML = angle;
fetch('/tilt?value=' + angle);
}
function movePan(offset) {
const slider = document.getElementById("panSlider");
let newAngle = parseInt(slider.value) + offset;
if (newAngle < 0) newAngle = 0;
if (newAngle > 180) newAngle = 180;
slider.value = newAngle;
updatePan(newAngle);
}
function moveTilt(offset) {
const slider = document.getElementById("tiltSlider");
let newAngle = parseInt(slider.value) + offset;
if (newAngle < 0) newAngle = 0;
if (newAngle > 180) newAngle = 180;
slider.value = newAngle;
updateTilt(newAngle);
}
function centerPan() {
document.getElementById("panSlider").value = 90;
updatePan(90);
}
function centerTilt() {
document.getElementById("tiltSlider").value = 90;
updateTilt(90);
}
function setPreset(pan, tilt) {
document.getElementById("panSlider").value = pan;
document.getElementById("tiltSlider").value = tilt;
updatePan(pan);
updateTilt(tilt);
}
function rotateVertical() {
fetch('/control?cmd=vertical')
.then(response => { console.log('Vertical flip toggled'); });
}
function rotateHorizontal() {
fetch('/control?cmd=horizontal')
.then(response => { console.log('Horizontal flip toggled'); });
}
function resetRotation() {
fetch('/control?cmd=reset')
.then(response => { console.log('Rotation reset'); });
}
function openTab(evt, tabName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(tabName).style.display = "block";
evt.currentTarget.className += " active";
// Auto-refresh stream when video tab is opened
if (tabName === 'video') {
var img = document.getElementById("videoStream");
function updateImage() {
img.src = '/stream?' + new Date().getTime();
}
setInterval(updateImage, 0);
}
// Auto-scan when STA tab is opened
if (tabName === 'sta') {
setTimeout(scanNetworks, 500);
}
}
</script>
</body>
</html>
)rawliteral";
String processor(const String& var) {
if (var == "PAN") return String(panAngle);
else if (var == "TILT") return String(tiltAngle);
else if (var == "STATUS") {
String status;
if (WiFi.getMode() & WIFI_STA) {
if (WiFi.status() == WL_CONNECTED) {
status += "<p>STA Mode: Connected to " + String(wifiConfig.sta_ssid) + "</p>";
status += "<p>IP Address: " + WiFi.localIP().toString() + "</p>";
} else {
status += "<p>STA Mode: Not connected (" + String(wifiConfig.sta_ssid) + ")</p>";
}
}
if (WiFi.getMode() & WIFI_AP) {
status += "<p>AP Mode: " + String(wifiConfig.ap_ssid) + "</p>";
status += "<p>AP IP: " + WiFi.softAPIP().toString() + "</p>";
}
return status;
}
else if (var == "STA_SSID") return String(wifiConfig.sta_ssid);
else if (var == "AP_SSID") return String(wifiConfig.ap_ssid);
else if (var == "AP_PASSWORD") return String(wifiConfig.ap_password);
else if (var == "AP_IP") return IPAddress(wifiConfig.ap_ip).toString();
else if (var == "AP_GATEWAY") return IPAddress(wifiConfig.ap_gateway).toString();
else if (var == "AP_SUBNET") return IPAddress(wifiConfig.ap_subnet).toString();
return String();
}
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(vertical_flip || horizontal_flip) {
uint8_t *buf = fb->buf;
int width = fb->width;
int height = fb->height;
int bytes_per_pixel = fb->len / (width * height);
uint8_t *flipped_buf = (uint8_t *)malloc(fb->len);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int src_x = horizontal_flip ? (width - 1 - x) : x;
int src_y = vertical_flip ? (height - 1 - y) : y;
for (int b = 0; b < bytes_per_pixel; b++) {
flipped_buf[(y * width + x) * bytes_per_pixel + b] =
buf[(src_y * width + src_x) * bytes_per_pixel + b];
}
}
}
_jpg_buf = flipped_buf;
_jpg_buf_len = fb->len;
} else {
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;
} else if(_jpg_buf && (vertical_flip || horizontal_flip)){
free(_jpg_buf);
_jpg_buf = NULL;
}
if(res != ESP_OK){
break;
}
}
}
return res;
}
static esp_err_t cmd_handler(httpd_req_t *req) {
char* buf;
size_t buf_len;
char variable[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, "cmd", variable, sizeof(variable)) == ESP_OK) {
if (strcmp(variable, "vertical") == 0) {
vertical_flip = !vertical_flip;
Serial.println("Vertical flip toggled");
} else if (strcmp(variable, "horizontal") == 0) {
horizontal_flip = !horizontal_flip;
Serial.println("Horizontal flip toggled");
} else if (strcmp(variable, "reset") == 0) {
vertical_flip = false;
horizontal_flip = false;
Serial.println("Rotation reset");
}
}
}
free(buf);
}
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
void startCameraServer() {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 80;
httpd_uri_t stream_uri = {
.uri = "/stream",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
httpd_uri_t cmd_uri = {
.uri = "/control",
.method = HTTP_GET,
.handler = cmd_handler,
.user_ctx = NULL
};
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
httpd_register_uri_handler(stream_httpd, &stream_uri);
httpd_register_uri_handler(stream_httpd, &cmd_uri);
}
}
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;
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_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Disable brownout detector
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
}
void loadConfig() {
EEPROM.get(EEPROM_START, wifiConfig);
if (wifiConfig.mode > 2) {
memset(&wifiConfig, 0, sizeof(wifiConfig));
strcpy(wifiConfig.ap_ssid, "ESP32-CAM");
strcpy(wifiConfig.ap_password, "password123");
wifiConfig.ap_ip = (uint32_t)IPAddress(192, 168, 4, 1);
wifiConfig.ap_gateway = (uint32_t)IPAddress(192, 168, 4, 1);
wifiConfig.ap_subnet = (uint32_t)IPAddress(255, 255, 255, 0);
wifiConfig.mode = 1;
}
}
void saveConfig() {
EEPROM.put(EEPROM_START, wifiConfig);
EEPROM.commit();
}
void startWiFi() {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
delay(100);
if (wifiConfig.mode == 0 || wifiConfig.mode == 2) {
IPAddress staticIP(wifiConfig.sta_ip);
IPAddress gateway(wifiConfig.sta_gateway);
IPAddress subnet(wifiConfig.sta_subnet);
if (wifiConfig.sta_ip != 0) {
WiFi.config(staticIP, gateway, subnet);
}
WiFi.begin(wifiConfig.sta_ssid, wifiConfig.sta_password);
Serial.print("Connecting to WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nConnected to WiFi");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nFailed to connect to WiFi");
}
}
if (wifiConfig.mode == 1 || wifiConfig.mode == 2) {
IPAddress apIP(wifiConfig.ap_ip);
IPAddress gateway(wifiConfig.ap_gateway);
IPAddress subnet(wifiConfig.ap_subnet);
WiFi.softAPConfig(apIP, gateway, subnet);
WiFi.softAP(wifiConfig.ap_ssid, wifiConfig.ap_password);
Serial.println("AP Mode Enabled");
Serial.print("AP SSID: ");
Serial.println(wifiConfig.ap_ssid);
Serial.print("AP IP Address: ");
Serial.println(apIP);
}
}
void handleScan() {
WiFiMode_t currentMode = WiFi.getMode();
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
int n = WiFi.scanNetworks();
String json = "[";
if (n == -1) {
json += "{\"error\":\"Scan failed\"}";
} else if (n == 0) {
json += "{\"error\":\"No networks found\"}";
} else {
for (int i = 0; i < n; ++i) {
if (i) json += ",";
json += "{";
json += "\"ssid\":\"" + WiFi.SSID(i) + "\",";
json += "\"rssi\":" + String(WiFi.RSSI(i)) + ",";
json += "\"encryption\":" + String(WiFi.encryptionType(i) != WIFI_AUTH_OPEN);
json += "}";
delay(10);
}
}
json += "]";
WiFi.mode(currentMode);
if (currentMode & WIFI_AP) {
WiFi.softAP(wifiConfig.ap_ssid, wifiConfig.ap_password);
}
server.send(200, "application/json", json);
WiFi.scanDelete();
}
void handleSave() {
if (server.method() != HTTP_POST) {
server.send(405, "text/plain", "Method Not Allowed");
return;
}
String mode = server.arg("mode");
if (mode == "0") {
String ssid = server.arg("sta_ssid");
String password = server.arg("sta_password");
String ip = server.arg("sta_ip");
String gateway = server.arg("sta_gateway");
String subnet = server.arg("sta_subnet");
if (ssid.length() > 0) {
ssid.toCharArray(wifiConfig.sta_ssid, sizeof(wifiConfig.sta_ssid));
password.toCharArray(wifiConfig.sta_password, sizeof(wifiConfig.sta_password));
if (ip.length() > 0) {
wifiConfig.sta_ip = IPAddress().fromString(ip);
} else {
wifiConfig.sta_ip = 0;
}
if (gateway.length() > 0) {
wifiConfig.sta_gateway = IPAddress().fromString(gateway);
} else {
wifiConfig.sta_gateway = 0;
}
if (subnet.length() > 0) {
wifiConfig.sta_subnet = IPAddress().fromString(subnet);
} else {
wifiConfig.sta_subnet = 0;
}
wifiConfig.mode = 0;
saveConfig();
server.send(200, "text/html", "<script>alert('STA Configuration Saved! Device will reboot.'); setTimeout(function(){ window.location.href = '/reboot'; }, 1000);</script>");
return;
}
} else if (mode == "1") {
String ssid = server.arg("ap_ssid");
String password = server.arg("ap_password");
String ip = server.arg("ap_ip");
String gateway = server.arg("ap_gateway");
String subnet = server.arg("ap_subnet");
if (ssid.length() > 0 && ip.length() > 0) {
ssid.toCharArray(wifiConfig.ap_ssid, sizeof(wifiConfig.ap_ssid));
password.toCharArray(wifiConfig.ap_password, sizeof(wifiConfig.ap_password));
wifiConfig.ap_ip = IPAddress().fromString(ip);
wifiConfig.ap_gateway = IPAddress().fromString(gateway);
wifiConfig.ap_subnet = IPAddress().fromString(subnet);
wifiConfig.mode = 1;
saveConfig();
server.send(200, "text/html", "<script>alert('AP Configuration Saved! Device will reboot.'); setTimeout(function(){ window.location.href = '/reboot'; }, 1000);</script>");
return;
}
}
server.send(400, "text/plain", "Invalid Parameters");
}
void handleReboot() {
server.send(200, "text/html", "<!DOCTYPE html><html><head><meta http-equiv=\"refresh\" content=\"5;url=/setup\"></head><body><h1>Rebooting...</h1><p>Device will restart in 5 seconds.</p></body></html>");
delay(1000);
ESP.restart();
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
pinMode(FLASH_GPIO_NUM, OUTPUT);
digitalWrite(FLASH_GPIO_NUM, LOW);
EEPROM.begin(EEPROM_SIZE);
loadConfig();
panServo.attach(PAN_PIN);
tiltServo.attach(TILT_PIN);
panServo.write(panAngle);
tiltServo.write(tiltAngle);
startWiFi();
setupCamera();
startCameraServer();
server.on("/", HTTP_GET, []() {
String html = mainPageContent;
html.replace("%PAN%", String(panAngle));
html.replace("%TILT%", String(tiltAngle));
server.send(200, "text/html", html);
});
server.on("/setup", HTTP_GET, []() {
String html = wifiConfigPage;
html.replace("%STATUS%", processor("STATUS"));
html.replace("%STA_SSID%", processor("STA_SSID"));
html.replace("%AP_SSID%", processor("AP_SSID"));
html.replace("%AP_PASSWORD%", processor("AP_PASSWORD"));
html.replace("%AP_IP%", processor("AP_IP"));
html.replace("%AP_GATEWAY%", processor("AP_GATEWAY"));
html.replace("%AP_SUBNET%", processor("AP_SUBNET"));
server.send(200, "text/html", html);
});
server.on("/pan", HTTP_GET, []() {
if (server.hasArg("value")) {
panAngle = server.arg("value").toInt();
panServo.write(panAngle);
}
server.send(200, "text/plain", "OK");
});
server.on("/tilt", HTTP_GET, []() {
if (server.hasArg("value")) {
tiltAngle = server.arg("value").toInt();
tiltServo.write(tiltAngle);
}
server.send(200, "text/plain", "OK");
});
server.on("/scan", HTTP_GET, handleScan);
server.on("/save", HTTP_POST, handleSave);
server.on("/reboot", HTTP_POST, handleReboot);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
delay(2);
}
Atau untuk versi lebih mudahnya ini dia versi binary :
yang dapat di-upload melalui ini oleh spacehuhn