first commit

This commit is contained in:
Jérôme Delacotte
2025-03-06 11:15:32 +01:00
commit 7b30d6e298
5276 changed files with 2108927 additions and 0 deletions

View File

@@ -0,0 +1,845 @@
#include <esp_camera.h>
#include <esp_int_wdt.h>
#include <esp_task_wdt.h>
#include <WiFi.h>
#include <DNSServer.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include "src/parsebytes.h"
#include "time.h"
#include <ESPmDNS.h>
/* This sketch is a extension/expansion/reork of the 'official' ESP32 Camera example
* sketch from Expressif:
* https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer
*
* It is modified to allow control of Illumination LED Lamps's (present on some modules),
* greater feedback via a status LED, and the HTML contents are present in plain text
* for easy modification.
*
* A camera name can now be configured, and wifi details can be stored in an optional
* header file to allow easier updated of the repo.
*
* The web UI has had changes to add the lamp control, rotation, a standalone viewer,
* more feeedback, new controls and other tweaks and changes,
* note: Make sure that you have either selected ESP32 AI Thinker,
* or another board which has PSRAM enabled to use high resolution camera modes
*/
/*
* FOR NETWORK AND HARDWARE SETTINGS COPY OR RENAME 'myconfig.sample.h' TO 'myconfig.h' AND EDIT THAT.
*
* By default this sketch will assume an AI-THINKER ESP-CAM and create
* an accesspoint called "ESP32-CAM-CONNECT" (password: "InsecurePassword")
*
*/
// Primary config, or defaults.
//#if __has_include("myconfig.h")
struct station { const char ssid[65]; const char password[65]; const bool dhcp;}; // do no edit
#include "myconfig.h"
//#else
// #warning "Using Defaults: Copy myconfig.sample.h to myconfig.h and edit that to use your own settings"
// #define WIFI_AP_ENABLE
// #define CAMERA_MODEL_AI_THINKER
// struct station { const char ssid[65]; const char password[65]; const bool dhcp;}
// stationList[] = {{"ESP32-CAM-CONNECT","InsecurePassword", true}};
//#endif
// Upstream version string
#include "src/version.h"
// Pin Mappings
#include "camera_pins.h"
// Camera config structure
camera_config_t config;
// Internal filesystem (SPIFFS)
// used for non-volatile camera settings
#include "storage.h"
// Sketch Info
int sketchSize;
int sketchSpace;
String sketchMD5;
// Start with accesspoint mode disabled, wifi setup will activate it if
// no known networks are found, and WIFI_AP_ENABLE has been defined
bool accesspoint = false;
// IP address, Netmask and Gateway, populated when connected
IPAddress ip;
IPAddress net;
IPAddress gw;
// Declare external function from app_httpd.cpp
extern void startCameraServer(int hPort, int sPort);
extern void serialDump();
// Names for the Camera. (set these in myconfig.h)
#if defined(CAM_NAME)
char myName[] = CAM_NAME;
#else
char myName[] = "ESP32 camera server";
#endif
#if defined(MDNS_NAME)
char mdnsName[] = MDNS_NAME;
#else
char mdnsName[] = "esp32-cam";
#endif
// Ports for http and stream (override in myconfig.h)
#if defined(HTTP_PORT)
int httpPort = HTTP_PORT;
#else
int httpPort = 80;
#endif
#if defined(STREAM_PORT)
int streamPort = STREAM_PORT;
#else
int streamPort = 81;
#endif
#if !defined(WIFI_WATCHDOG)
#define WIFI_WATCHDOG 15000
#endif
// Number of known networks in stationList[]
int stationCount = sizeof(stationList)/sizeof(stationList[0]);
// If we have AP mode enabled, ignore first entry in the stationList[]
#if defined(WIFI_AP_ENABLE)
int firstStation = 1;
#else
int firstStation = 0;
#endif
// Select between full and simple index as the default.
#if defined(DEFAULT_INDEX_FULL)
char default_index[] = "full";
#else
char default_index[] = "simple";
#endif
// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;
bool captivePortal = false;
char apName[64] = "Undefined";
// The app and stream URLs
char httpURL[64] = {"Undefined"};
char streamURL[64] = {"Undefined"};
// Counters for info screens and debug
int8_t streamCount = 0; // Number of currently active streams
unsigned long streamsServed = 0; // Total completed streams
unsigned long imagesServed = 0; // Total image requests
// This will be displayed to identify the firmware
char myVer[] PROGMEM = __DATE__ " @ " __TIME__;
// This will be set to the sensors PID (identifier) during initialisation
//camera_pid_t sensorPID;
int sensorPID;
// Camera module bus communications frequency.
// Originally: config.xclk_freq_mhz = 20000000, but this lead to visual artifacts on many modules.
// See https://github.com/espressif/esp32-camera/issues/150#issuecomment-726473652 et al.
#if !defined (XCLK_FREQ_MHZ)
unsigned long xclk = 8;
#else
unsigned long xclk = XCLK_FREQ_MHZ;
#endif
// initial rotation
// can be set in myconfig.h
#if !defined(CAM_ROTATION)
#define CAM_ROTATION 0
#endif
int myRotation = CAM_ROTATION;
// minimal frame duration in ms, effectively 1/maxFPS
#if !defined(MIN_FRAME_TIME)
#define MIN_FRAME_TIME 0
#endif
int minFrameTime = MIN_FRAME_TIME;
// Illumination LAMP and status LED
#if defined(LAMP_DISABLE)
int lampVal = -1; // lamp is disabled in config
#elif defined(LAMP_PIN)
#if defined(LAMP_DEFAULT)
int lampVal = constrain(LAMP_DEFAULT,0,100); // initial lamp value, range 0-100
#else
int lampVal = 0; //default to off
#endif
#else
int lampVal = -1; // no lamp pin assigned
#endif
#if defined(LED_DISABLE)
#undef LED_PIN // undefining this disables the notification LED
#endif
bool autoLamp = false; // Automatic lamp (auto on while camera running)
int lampChannel = 7; // a free PWM channel (some channels used by camera)
const int pwmfreq = 50000; // 50K pwm frequency
const int pwmresolution = 9; // duty cycle bit range
const int pwmMax = pow(2,pwmresolution)-1;
#if defined(NO_FS)
bool filesystem = false;
#else
bool filesystem = true;
#endif
#if defined(NO_OTA)
bool otaEnabled = false;
#else
bool otaEnabled = true;
#endif
#if defined(OTA_PASSWORD)
char otaPassword[] = OTA_PASSWORD;
#else
char otaPassword[] = "";
#endif
#if defined(NTPSERVER)
bool haveTime = true;
const char* ntpServer = NTPSERVER;
const long gmtOffset_sec = NTP_GMT_OFFSET;
const int daylightOffset_sec = NTP_DST_OFFSET;
#else
bool haveTime = false;
const char* ntpServer = "";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;
#endif
// Critical error string; if set during init (camera hardware failure) it
// will be returned for all http requests
String critERR = "";
// Debug flag for stream and capture data
bool debugData;
void debugOn() {
debugData = true;
Serial.println("Camera debug data is enabled (send 'd' for status dump, or any other char to disable debug)");
}
void debugOff() {
debugData = false;
Serial.println("Camera debug data is disabled (send 'd' for status dump, or any other char to enable debug)");
}
// Serial input (debugging controls)
void handleSerial() {
if (Serial.available()) {
char cmd = Serial.read();
if (cmd == 'd' ) {
serialDump();
} else {
if (debugData) debugOff();
else debugOn();
}
}
while (Serial.available()) Serial.read(); // chomp the buffer
}
// Notification LED
void flashLED(int flashtime) {
#if defined(LED_PIN) // If we have it; flash it.
digitalWrite(LED_PIN, LED_ON); // On at full power.
delay(flashtime); // delay
digitalWrite(LED_PIN, LED_OFF); // turn Off
#else
return; // No notifcation LED, do nothing, no delay
#endif
}
// Lamp Control
void setLamp(int newVal) {
#if defined(LAMP_PIN)
if (newVal != -1) {
// Apply a logarithmic function to the scale.
int brightness = round((pow(2,(1+(newVal*0.02)))-2)/6*pwmMax);
ledcWrite(lampChannel, brightness);
Serial.print("Lamp: ");
Serial.print(newVal);
Serial.print("%, pwm = ");
Serial.println(brightness);
}
#endif
}
void printLocalTime(bool extraData=false) {
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
} else {
Serial.println(&timeinfo, "%H:%M:%S, %A, %B %d %Y");
}
if (extraData) {
Serial.printf("NTP Server: %s, GMT Offset: %li(s), DST Offset: %i(s)\r\n", ntpServer, gmtOffset_sec, daylightOffset_sec);
}
}
void calcURLs() {
// Set the URL's
#if defined(URL_HOSTNAME)
if (httpPort != 80) {
sprintf(httpURL, "http://%s:%d/", URL_HOSTNAME, httpPort);
} else {
sprintf(httpURL, "http://%s/", URL_HOSTNAME);
}
sprintf(streamURL, "http://%s:%d/", URL_HOSTNAME, streamPort);
#else
Serial.println("Setting httpURL");
if (httpPort != 80) {
sprintf(httpURL, "http://%d.%d.%d.%d:%d/", ip[0], ip[1], ip[2], ip[3], httpPort);
} else {
sprintf(httpURL, "http://%d.%d.%d.%d/", ip[0], ip[1], ip[2], ip[3]);
}
sprintf(streamURL, "http://%d.%d.%d.%d:%d/", ip[0], ip[1], ip[2], ip[3], streamPort);
#endif
}
void StartCamera() {
// Populate camera config structure with hardware and other defaults
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 = xclk * 1000000;
config.pixel_format = PIXFORMAT_JPEG;
// Low(ish) default framesize and quality
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.fb_count = 2;
config.grab_mode = CAMERA_GRAB_LATEST;
#if defined(CAMERA_MODEL_ESP_EYE)
pinMode(13, INPUT_PULLUP);
pinMode(14, INPUT_PULLUP);
#endif
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
delay(100); // need a delay here or the next serial o/p gets missed
Serial.printf("\r\n\r\nCRITICAL FAILURE: Camera sensor failed to initialise.\r\n\r\n");
Serial.printf("A full (hard, power off/on) reboot will probably be needed to recover from this.\r\n");
Serial.printf("Meanwhile; this unit will reboot in 1 minute since these errors sometime clear automatically\r\n");
// Reset the I2C bus.. may help when rebooting.
periph_module_disable(PERIPH_I2C0_MODULE); // try to shut I2C down properly in case that is the problem
periph_module_disable(PERIPH_I2C1_MODULE);
periph_module_reset(PERIPH_I2C0_MODULE);
periph_module_reset(PERIPH_I2C1_MODULE);
// And set the error text for the UI
critERR = "<h1>Error!</h1><hr><p>Camera module failed to initialise!</p><p>Please reset (power off/on) the camera.</p>";
critERR += "<p>We will continue to reboot once per minute since this error sometimes clears automatically.</p>";
// Start a 60 second watchdog timer
esp_task_wdt_init(60,true);
esp_task_wdt_add(NULL);
} else {
Serial.println("Camera init succeeded");
// Get a reference to the sensor
sensor_t * s = esp_camera_sensor_get();
// Dump camera module, warn for unsupported modules.
sensorPID = s->id.PID;
switch (sensorPID) {
case OV9650_PID: Serial.println("WARNING: OV9650 camera module is not properly supported, will fallback to OV2640 operation"); break;
case OV7725_PID: Serial.println("WARNING: OV7725 camera module is not properly supported, will fallback to OV2640 operation"); break;
case OV2640_PID: Serial.println("OV2640 camera module detected"); break;
case OV3660_PID: Serial.println("OV3660 camera module detected"); break;
default: Serial.println("WARNING: Camera module is unknown and not properly supported, will fallback to OV2640 operation");
}
// OV3660 initial sensors are flipped vertically and colors are a bit saturated
if (sensorPID == OV3660_PID) {
s->set_vflip(s, 1); //flip it back
s->set_brightness(s, 1); //up the blightness just a bit
s->set_saturation(s, -2); //lower the saturation
}
// M5 Stack Wide has special needs
#if defined(CAMERA_MODEL_M5STACK_WIDE)
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
#endif
// Config can override mirror and flip
#if defined(H_MIRROR)
s->set_hmirror(s, H_MIRROR);
#endif
#if defined(V_FLIP)
s->set_vflip(s, V_FLIP);
#endif
// set initial frame rate
#if defined(DEFAULT_RESOLUTION)
s->set_framesize(s, DEFAULT_RESOLUTION);
#endif
/*
* Add any other defaults you want to apply at startup here:
* uncomment the line and set the value as desired (see the comments)
*
* these are defined in the esp headers here:
* https://github.com/espressif/esp32-camera/blob/master/driver/include/sensor.h#L149
*/
//s->set_framesize(s, FRAMESIZE_SVGA); // FRAMESIZE_[QQVGA|HQVGA|QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA|QXGA(ov3660)]);
//s->set_quality(s, val); // 10 to 63
//s->set_brightness(s, 0); // -2 to 2
//s->set_contrast(s, 0); // -2 to 2
//s->set_saturation(s, 0); // -2 to 2
//s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
//s->set_whitebal(s, 1); // aka 'awb' in the UI; 0 = disable , 1 = enable
//s->set_awb_gain(s, 1); // 0 = disable , 1 = enable
//s->set_wb_mode(s, 0); // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
//s->set_exposure_ctrl(s, 1); // 0 = disable , 1 = enable
//s->set_aec2(s, 0); // 0 = disable , 1 = enable
//s->set_ae_level(s, 0); // -2 to 2
//s->set_aec_value(s, 300); // 0 to 1200
//s->set_gain_ctrl(s, 1); // 0 = disable , 1 = enable
//s->set_agc_gain(s, 0); // 0 to 30
//s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6
//s->set_bpc(s, 0); // 0 = disable , 1 = enable
//s->set_wpc(s, 1); // 0 = disable , 1 = enable
//s->set_raw_gma(s, 1); // 0 = disable , 1 = enable
//s->set_lenc(s, 1); // 0 = disable , 1 = enable
//s->set_hmirror(s, 0); // 0 = disable , 1 = enable
//s->set_vflip(s, 0); // 0 = disable , 1 = enable
//s->set_dcw(s, 1); // 0 = disable , 1 = enable
//s->set_colorbar(s, 0); // 0 = disable , 1 = enable
}
// We now have camera with default init
}
void WifiSetup() {
// Feedback that we are now attempting to connect
flashLED(300);
delay(100);
flashLED(300);
Serial.println("Starting WiFi");
// Disable power saving on WiFi to improve responsiveness
// (https://github.com/espressif/arduino-esp32/issues/1484)
WiFi.setSleep(false);
Serial.print("Known external SSIDs: ");
if (stationCount > firstStation) {
for (int i=firstStation; i < stationCount; i++) Serial.printf(" '%s'", stationList[i].ssid);
} else {
Serial.print("None");
}
Serial.println();
byte mac[6] = {0,0,0,0,0,0};
WiFi.macAddress(mac);
Serial.printf("MAC address: %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
int bestStation = -1;
long bestRSSI = -1024;
char bestSSID[65] = "";
uint8_t bestBSSID[6];
if (stationCount > firstStation) {
// We have a list to scan
Serial.printf("Scanning local Wifi Networks\r\n");
int stationsFound = WiFi.scanNetworks();
Serial.printf("%i networks found\r\n", stationsFound);
if (stationsFound > 0) {
for (int i = 0; i < stationsFound; ++i) {
// Print SSID and RSSI for each network found
String thisSSID = WiFi.SSID(i);
int thisRSSI = WiFi.RSSI(i);
String thisBSSID = WiFi.BSSIDstr(i);
Serial.printf("%3i : [%s] %s (%i)", i + 1, thisBSSID.c_str(), thisSSID.c_str(), thisRSSI);
// Scan our list of known external stations
for (int sta = firstStation; sta < stationCount; sta++) {
if ((strcmp(stationList[sta].ssid, thisSSID.c_str()) == 0) ||
(strcmp(stationList[sta].ssid, thisBSSID.c_str()) == 0)) {
Serial.print(" - Known!");
// Chose the strongest RSSI seen
if (thisRSSI > bestRSSI) {
bestStation = sta;
strncpy(bestSSID, thisSSID.c_str(), 64);
// Convert char bssid[] to a byte array
parseBytes(thisBSSID.c_str(), ':', bestBSSID, 6, 16);
bestRSSI = thisRSSI;
}
}
}
Serial.println();
}
}
} else {
// No list to scan, therefore we are an accesspoint
accesspoint = true;
}
if (bestStation == -1) {
if (!accesspoint) {
#if defined(WIFI_AP_ENABLE)
Serial.println("No known networks found, entering AccessPoint fallback mode");
accesspoint = true;
#else
Serial.println("No known networks found");
#endif
} else {
Serial.println("AccessPoint mode selected in config");
}
} else {
Serial.printf("Connecting to Wifi Network %d: [%02X:%02X:%02X:%02X:%02X:%02X] %s \r\n",
bestStation, bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3],
bestBSSID[4], bestBSSID[5], bestSSID);
// Apply static settings if necesscary
if (stationList[bestStation].dhcp == false) {
#if defined(ST_IP)
Serial.println("Applying static IP settings");
#if !defined (ST_GATEWAY) || !defined (ST_NETMASK)
#error "You must supply both Gateway and NetMask when specifying a static IP address"
#endif
IPAddress staticIP(ST_IP);
IPAddress gateway(ST_GATEWAY);
IPAddress subnet(ST_NETMASK);
#if !defined(ST_DNS1)
WiFi.config(staticIP, gateway, subnet);
#else
IPAddress dns1(ST_DNS1);
#if !defined(ST_DNS2)
WiFi.config(staticIP, gateway, subnet, dns1);
#else
IPAddress dns2(ST_DNS2);
WiFi.config(staticIP, gateway, subnet, dns1, dns2);
#endif
#endif
#else
Serial.println("Static IP settings requested but not defined in config, falling back to dhcp");
#endif
}
WiFi.setHostname(mdnsName);
// Initiate network connection request (3rd argument, channel = 0 is 'auto')
WiFi.begin(bestSSID, stationList[bestStation].password, 0, bestBSSID);
// Wait to connect, or timeout
unsigned long start = millis();
while ((millis() - start <= WIFI_WATCHDOG) && (WiFi.status() != WL_CONNECTED)) {
delay(500);
Serial.print('.');
}
// If we have connected, inform user
if (WiFi.status() == WL_CONNECTED) {
Serial.println("Client connection succeeded");
accesspoint = false;
// Note IP details
ip = WiFi.localIP();
net = WiFi.subnetMask();
gw = WiFi.gatewayIP();
Serial.printf("IP address: %d.%d.%d.%d\r\n",ip[0],ip[1],ip[2],ip[3]);
Serial.printf("Netmask : %d.%d.%d.%d\r\n",net[0],net[1],net[2],net[3]);
Serial.printf("Gateway : %d.%d.%d.%d\r\n",gw[0],gw[1],gw[2],gw[3]);
calcURLs();
// Flash the LED to show we are connected
for (int i = 0; i < 5; i++) {
flashLED(50);
delay(150);
}
} else {
Serial.println("Client connection Failed");
WiFi.disconnect(); // (resets the WiFi scan)
}
}
if (accesspoint && (WiFi.status() != WL_CONNECTED)) {
// The accesspoint has been enabled, and we have not connected to any existing networks
#if defined(AP_CHAN)
Serial.println("Setting up Fixed Channel AccessPoint");
Serial.print(" SSID : ");
Serial.println(stationList[0].ssid);
Serial.print(" Password : ");
Serial.println(stationList[0].password);
Serial.print(" Channel : ");
Serial.println(AP_CHAN);
WiFi.softAP(stationList[0].ssid, stationList[0].password, AP_CHAN);
# else
Serial.println("Setting up AccessPoint");
Serial.print(" SSID : ");
Serial.println(stationList[0].ssid);
Serial.print(" Password : ");
Serial.println(stationList[0].password);
WiFi.softAP(stationList[0].ssid, stationList[0].password);
#endif
#if defined(AP_ADDRESS)
// User has specified the AP details; apply them after a short delay
// (https://github.com/espressif/arduino-esp32/issues/985#issuecomment-359157428)
delay(100);
IPAddress local_IP(AP_ADDRESS);
IPAddress gateway(AP_ADDRESS);
IPAddress subnet(255,255,255,0);
WiFi.softAPConfig(local_IP, gateway, subnet);
#endif
// Note AP details
ip = WiFi.softAPIP();
net = WiFi.subnetMask();
gw = WiFi.gatewayIP();
strcpy(apName, stationList[0].ssid);
Serial.printf("IP address: %d.%d.%d.%d\r\n",ip[0],ip[1],ip[2],ip[3]);
calcURLs();
// Flash the LED to show we are connected
for (int i = 0; i < 5; i++) {
flashLED(150);
delay(50);
}
// Start the DNS captive portal if requested
if (stationList[0].dhcp == true) {
Serial.println("Starting Captive Portal");
dnsServer.start(DNS_PORT, "*", ip);
captivePortal = true;
}
}
}
void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Serial.println();
Serial.println("====");
Serial.print("esp32-cam-webserver: ");
Serial.println(myName);
Serial.print("Code Built: ");
Serial.println(myVer);
Serial.print("Base Release: ");
Serial.println(baseVersion);
Serial.println();
// Warn if no PSRAM is detected (typically user error with board selection in the IDE)
if(!psramFound()){
Serial.println("\r\nFatal Error; Halting");
while (true) {
Serial.println("No PSRAM found; camera cannot be initialised: Please check the board config for your module.");
delay(5000);
}
}
if (stationCount == 0) {
Serial.println("\r\nFatal Error; Halting");
while (true) {
Serial.println("No wifi details have been configured; we cannot connect to existing WiFi or start our own AccessPoint, there is no point in proceeding.");
delay(5000);
}
}
#if defined(LED_PIN) // If we have a notification LED, set it to output
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LED_ON);
#endif
// Start the SPIFFS filesystem before we initialise the camera
if (filesystem) {
filesystemStart();
delay(200); // a short delay to let spi bus settle after SPIFFS init
}
// Start (init) the camera
StartCamera();
// Now load and apply any saved preferences
if (filesystem) {
delay(200); // a short delay to let spi bus settle after camera init
loadPrefs(SPIFFS);
} else {
Serial.println("No Internal Filesystem, cannot load or save preferences");
}
/*
* Camera setup complete; initialise the rest of the hardware.
*/
// Start Wifi and loop until we are connected or have started an AccessPoint
while ((WiFi.status() != WL_CONNECTED) && !accesspoint) {
WifiSetup();
delay(1000);
}
// Set up OTA
if (otaEnabled) {
// Start OTA once connected
Serial.println("Setting up OTA");
// Port defaults to 3232
// ArduinoOTA.setPort(3232);
// Hostname defaults to esp3232-[MAC]
ArduinoOTA.setHostname(mdnsName);
// No authentication by default
if (strlen(otaPassword) != 0) {
ArduinoOTA.setPassword(otaPassword);
Serial.printf("OTA Password: %s\n\r", otaPassword);
} else {
Serial.printf("\r\nNo OTA password has been set! (insecure)\r\n\r\n");
}
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
type = "filesystem";
Serial.println("Start updating " + type);
// Stop the camera since OTA will crash the module if it is running.
// the unit will need rebooting to restart it, either by OTA on success, or manually by the user
Serial.println("Stopping Camera");
esp_err_t err = esp_camera_deinit();
critERR = "<h1>OTA Has been started</h1><hr><p>Camera has Halted!</p>";
critERR += "<p>Wait for OTA to finish and reboot, or <a href=\"control?var=reboot&val=0\" title=\"Reboot Now (may interrupt OTA)\">reboot manually</a> to recover</p>";
})
.onEnd([]() {
Serial.println("\r\nEnd");
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
} else {
Serial.println("OTA is disabled");
if (!MDNS.begin(mdnsName)) {
Serial.println("Error setting up MDNS responder!");
}
Serial.println("mDNS responder started");
}
//MDNS Config -- note that if OTA is NOT enabled this needs prior steps!
MDNS.addService("http", "tcp", 80);
Serial.println("Added HTTP service to MDNS server");
// Set time via NTP server when enabled
if (haveTime) {
Serial.print("Time: ");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime(true);
} else {
Serial.println("Time functions disabled");
}
// Gather static values used when dumping status; these are slow functions, so just do them once during startup
sketchSize = ESP.getSketchSize();
sketchSpace = ESP.getFreeSketchSpace();
sketchMD5 = ESP.getSketchMD5();
// Initialise and set the lamp
if (lampVal != -1) {
#if defined(LAMP_PIN)
ledcSetup(lampChannel, pwmfreq, pwmresolution); // configure LED PWM channel
ledcAttachPin(LAMP_PIN, lampChannel); // attach the GPIO pin to the channel
if (autoLamp) setLamp(0); // set default value
else setLamp(lampVal);
#endif
} else {
Serial.println("No lamp, or lamp disabled in config");
}
// Start the camera server
startCameraServer(httpPort, streamPort);
if (critERR.length() == 0) {
Serial.printf("\r\nCamera Ready!\r\nUse '%s' to connect\r\n", httpURL);
Serial.printf("Stream viewer available at '%sview'\r\n", streamURL);
Serial.printf("Raw stream URL is '%s'\r\n", streamURL);
#if defined(DEBUG_DEFAULT_ON)
debugOn();
#else
debugOff();
#endif
} else {
Serial.printf("\r\nCamera unavailable due to initialisation errors.\r\n\r\n");
}
// Info line; use for Info messages; eg 'This is a Beta!' warnings, etc. as necesscary
// Serial.print("\r\nThis is the 4.1 beta\r\n");
// As a final init step chomp out the serial buffer in case we have recieved mis-keys or garbage during startup
while (Serial.available()) Serial.read();
}
void loop() {
/*
* Just loop forever, reconnecting Wifi As necesscary in client mode
* The stream and URI handler processes initiated by the startCameraServer() call at the
* end of setup() will handle the camera and UI processing from now on.
*/
if (accesspoint) {
// Accespoint is permanently up, so just loop, servicing the captive portal as needed
// Rather than loop forever, follow the watchdog, in case we later add auto re-scan.
unsigned long start = millis();
while (millis() - start < WIFI_WATCHDOG ) {
delay(100);
if (otaEnabled) ArduinoOTA.handle();
handleSerial();
if (captivePortal) dnsServer.processNextRequest();
}
} else {
// client mode can fail; so reconnect as appropriate
static bool warned = false;
if (WiFi.status() == WL_CONNECTED) {
// We are connected, wait a bit and re-check
if (warned) {
// Tell the user if we have just reconnected
Serial.println("WiFi reconnected");
warned = false;
}
// loop here for WIFI_WATCHDOG, turning debugData true/false depending on serial input..
unsigned long start = millis();
while (millis() - start < WIFI_WATCHDOG ) {
delay(100);
if (otaEnabled) ArduinoOTA.handle();
handleSerial();
}
} else {
// disconnected; attempt to reconnect
if (!warned) {
// Tell the user if we just disconnected
WiFi.disconnect(); // ensures disconnect is complete, wifi scan cleared
Serial.println("WiFi disconnected, retrying");
warned = true;
}
WifiSetup();
}
}
}

885
ESP32_CAM_NEW/app_httpd.cpp Normal file
View File

@@ -0,0 +1,885 @@
// Original Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <esp_http_server.h>
#include <esp_timer.h>
#include <esp_camera.h>
#include <esp_int_wdt.h>
#include <esp_task_wdt.h>
#include <Arduino.h>
#include <WiFi.h>
#include "index_ov2640.h"
#include "index_ov3660.h"
#include "index_other.h"
#include "css.h"
#include "src/favicons.h"
#include "src/logo.h"
#include "storage.h"
// Functions from the main .ino
extern void flashLED(int flashtime);
extern void setLamp(int newVal);
extern void printLocalTime(bool extraData);
// External variables declared in the main .ino
extern char myName[];
extern char myVer[];
extern char baseVersion[];
extern IPAddress ip;
extern IPAddress net;
extern IPAddress gw;
extern bool accesspoint;
extern char apName[];
extern bool captivePortal;
extern int httpPort;
extern int streamPort;
extern char httpURL[];
extern char streamURL[];
extern char default_index[];
extern int8_t streamCount;
extern unsigned long streamsServed;
extern unsigned long imagesServed;
extern int myRotation;
extern int minFrameTime;
extern int lampVal;
extern bool autoLamp;
extern bool filesystem;
extern String critERR;
extern bool debugData;
extern bool haveTime;
extern int sketchSize;
extern int sketchSpace;
extern String sketchMD5;
extern bool otaEnabled;
extern char otaPassword[];
extern unsigned long xclk;
extern int sensorPID;
typedef struct {
httpd_req_t *req;
size_t len;
} jpg_chunking_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: image/jpeg\r\nContent-Length: %u\r\n\r\n";
httpd_handle_t stream_httpd = NULL;
httpd_handle_t camera_httpd = NULL;
// Flag that can be set to kill all active streams
bool streamKill;
#ifdef __cplusplus
extern "C" {
#endif
uint8_t temprature_sens_read();
#ifdef __cplusplus
}
#endif
void serialDump() {
Serial.println();
// Module
Serial.printf("Name: %s\r\n", myName);
if (haveTime) {
Serial.print("Time: ");
printLocalTime(true);
}
Serial.printf("Firmware: %s (base: %s)\r\n", myVer, baseVersion);
float sketchPct = 100 * sketchSize / sketchSpace;
Serial.printf("Sketch Size: %i (total: %i, %.1f%% used)\r\n", sketchSize, sketchSpace, sketchPct);
Serial.printf("MD5: %s\r\n", sketchMD5.c_str());
Serial.printf("ESP sdk: %s\r\n", ESP.getSdkVersion());
if (otaEnabled) {
if (strlen(otaPassword) != 0) {
Serial.printf("OTA: Enabled, Password: %s\n\r", otaPassword);
} else {
Serial.printf("OTA: Enabled, No Password! (insecure)\n\r");
}
} else {
Serial.printf("OTA: Disabled\n\r");
}
// Network
if (accesspoint) {
if (captivePortal) {
Serial.printf("WiFi Mode: AccessPoint with captive portal\r\n");
} else {
Serial.printf("WiFi Mode: AccessPoint\r\n");
}
Serial.printf("WiFi SSID: %s\r\n", apName);
} else {
Serial.printf("WiFi Mode: Client\r\n");
String ssidName = WiFi.SSID();
Serial.printf("WiFi Ssid: %s\r\n", ssidName.c_str());
Serial.printf("WiFi Rssi: %i\r\n", WiFi.RSSI());
String bssid = WiFi.BSSIDstr();
Serial.printf("WiFi BSSID: %s\r\n", bssid.c_str());
}
Serial.printf("WiFi IP address: %d.%d.%d.%d\r\n", ip[0], ip[1], ip[2], ip[3]);
if (!accesspoint) {
Serial.printf("WiFi Netmask: %d.%d.%d.%d\r\n", net[0], net[1], net[2], net[3]);
Serial.printf("WiFi Gateway: %d.%d.%d.%d\r\n", gw[0], gw[1], gw[2], gw[3]);
}
Serial.printf("WiFi Http port: %i, Stream port: %i\r\n", httpPort, streamPort);
byte mac[6];
WiFi.macAddress(mac);
Serial.printf("WiFi MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// System
int64_t sec = esp_timer_get_time() / 1000000;
int64_t upDays = int64_t(floor(sec/86400));
int upHours = int64_t(floor(sec/3600)) % 24;
int upMin = int64_t(floor(sec/60)) % 60;
int upSec = sec % 60;
int McuTc = (temprature_sens_read() - 32) / 1.8; // celsius
int McuTf = temprature_sens_read(); // fahrenheit
Serial.printf("System up: %" PRId64 ":%02i:%02i:%02i (d:h:m:s)\r\n", upDays, upHours, upMin, upSec);
Serial.printf("Active streams: %i, Previous streams: %lu, Images captured: %lu\r\n", streamCount, streamsServed, imagesServed);
Serial.printf("CPU Freq: %i MHz, Xclk Freq: %i MHz\r\n", ESP.getCpuFreqMHz(), xclk);
Serial.printf("MCU temperature : %i C, %i F (approximate)\r\n", McuTc, McuTf);
Serial.printf("Heap: %i, free: %i, min free: %i, max block: %i\r\n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
if(psramFound()) {
Serial.printf("Psram: %i, free: %i, min free: %i, max block: %i\r\n", ESP.getPsramSize(), ESP.getFreePsram(), ESP.getMinFreePsram(), ESP.getMaxAllocPsram());
} else {
Serial.printf("Psram: Not found; please check your board configuration.\r\n");
Serial.printf("- High resolution/quality settings will show incomplete frames to low memory.\r\n");
}
// Filesystems
if (filesystem && (SPIFFS.totalBytes() > 0)) {
Serial.printf("Spiffs: %i, used: %i\r\n", SPIFFS.totalBytes(), SPIFFS.usedBytes());
} else {
Serial.printf("Spiffs: No filesystem found, please check your board configuration.\r\n");
Serial.printf("- Saving and restoring camera settings will not function without this.\r\n");
}
Serial.println("Preferences file: ");
dumpPrefs(SPIFFS);
if (critERR.length() > 0) {
Serial.printf("\r\n\r\nAn error or halt has occurred with Camera Hardware, see previous messages.\r\n");
Serial.printf("A reboot is required to recover from this.\r\nError message: (html)\r\n %s\r\n\r\n", critERR.c_str());
}
Serial.println();
return;
}
static esp_err_t capture_handler(httpd_req_t *req){
camera_fb_t * fb = NULL;
esp_err_t res = ESP_OK;
Serial.println("Capture Requested");
if (autoLamp && (lampVal != -1)) {
setLamp(lampVal);
delay(75); // coupled with the status led flash this gives ~150ms for lamp to settle.
}
flashLED(75); // little flash of status LED
int64_t fr_start = esp_timer_get_time();
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("CAPTURE: failed to acquire frame");
httpd_resp_send_500(req);
if (autoLamp && (lampVal != -1)) setLamp(0);
return ESP_FAIL;
}
httpd_resp_set_type(req, "image/jpeg");
httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
size_t fb_len = 0;
if(fb->format == PIXFORMAT_JPEG){
fb_len = fb->len;
res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
} else {
res = ESP_FAIL;
Serial.println("Capture Error: Non-JPEG image returned by camera module");
}
esp_camera_fb_return(fb);
fb = NULL;
int64_t fr_end = esp_timer_get_time();
if (debugData) {
Serial.printf("JPG: %uB %ums\r\n", (uint32_t)(fb_len), (uint32_t)((fr_end - fr_start)/1000));
}
imagesServed++;
if (autoLamp && (lampVal != -1)) {
setLamp(0);
}
return res;
}
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];
streamKill = false;
Serial.println("Stream requested");
if (autoLamp && (lampVal != -1)) setLamp(lampVal);
streamCount = 1; // at present we only have one stream handler, so values are 0 or 1..
flashLED(75); // double flash of status LED
delay(75);
flashLED(75);
static int64_t last_frame = 0;
if(!last_frame) {
last_frame = esp_timer_get_time();
}
res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
if(res != ESP_OK){
streamCount = 0;
if (autoLamp && (lampVal != -1)) setLamp(0);
Serial.println("STREAM: failed to set HTTP response type");
return res;
}
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
if(res == ESP_OK){
res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
}
while(true){
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("STREAM: failed to acquire frame");
res = ESP_FAIL;
} else {
if(fb->format != PIXFORMAT_JPEG){
Serial.println("STREAM: Non-JPEG frame returned by camera module");
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){
// This is the error exit point from the stream loop.
// We end the stream here only if a Hard failure has been encountered or the connection has been interrupted.
Serial.printf("Stream failed, code = %i : %s\r\n", res, esp_err_to_name(res));
break;
}
if((res != ESP_OK) || streamKill){
// We end the stream here when a kill is signalled.
Serial.printf("Stream killed\r\n");
break;
}
int64_t frame_time = esp_timer_get_time() - last_frame;
frame_time /= 1000;
int32_t frame_delay = (minFrameTime > frame_time) ? minFrameTime - frame_time : 0;
delay(frame_delay);
if (debugData) {
Serial.printf("MJPG: %uB %ums, delay: %ums, framerate (%.1ffps)\r\n",
(uint32_t)(_jpg_buf_len),
(uint32_t)frame_time, frame_delay, 1000.0 / (uint32_t)(frame_time + frame_delay));
}
last_frame = esp_timer_get_time();
}
streamsServed++;
streamCount = 0;
if (autoLamp && (lampVal != -1)) setLamp(0);
Serial.println("Stream ended");
last_frame = 0;
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,};
flashLED(75);
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;
}
if (critERR.length() > 0) return httpd_resp_send_500(req);
int val = atoi(value);
sensor_t * s = esp_camera_sensor_get();
int res = 0;
if(!strcmp(variable, "framesize")) {
if(s->pixformat == PIXFORMAT_JPEG) res = s->set_framesize(s, (framesize_t)val);
}
else if(!strcmp(variable, "quality")) res = s->set_quality(s, val);
else if(!strcmp(variable, "xclk")) { xclk = val; res = s->set_xclk(s, LEDC_TIMER_0, val); }
else if(!strcmp(variable, "contrast")) res = s->set_contrast(s, val);
else if(!strcmp(variable, "brightness")) res = s->set_brightness(s, val);
else if(!strcmp(variable, "saturation")) res = s->set_saturation(s, val);
else if(!strcmp(variable, "gainceiling")) res = s->set_gainceiling(s, (gainceiling_t)val);
else if(!strcmp(variable, "colorbar")) res = s->set_colorbar(s, val);
else if(!strcmp(variable, "awb")) res = s->set_whitebal(s, val);
else if(!strcmp(variable, "agc")) res = s->set_gain_ctrl(s, val);
else if(!strcmp(variable, "aec")) res = s->set_exposure_ctrl(s, val);
else if(!strcmp(variable, "hmirror")) res = s->set_hmirror(s, val);
else if(!strcmp(variable, "vflip")) res = s->set_vflip(s, val);
else if(!strcmp(variable, "awb_gain")) res = s->set_awb_gain(s, val);
else if(!strcmp(variable, "agc_gain")) res = s->set_agc_gain(s, val);
else if(!strcmp(variable, "aec_value")) res = s->set_aec_value(s, val);
else if(!strcmp(variable, "aec2")) res = s->set_aec2(s, val);
else if(!strcmp(variable, "dcw")) res = s->set_dcw(s, val);
else if(!strcmp(variable, "bpc")) res = s->set_bpc(s, val);
else if(!strcmp(variable, "wpc")) res = s->set_wpc(s, val);
else if(!strcmp(variable, "raw_gma")) res = s->set_raw_gma(s, val);
else if(!strcmp(variable, "lenc")) res = s->set_lenc(s, val);
else if(!strcmp(variable, "special_effect")) res = s->set_special_effect(s, val);
else if(!strcmp(variable, "wb_mode")) res = s->set_wb_mode(s, val);
else if(!strcmp(variable, "ae_level")) res = s->set_ae_level(s, val);
else if(!strcmp(variable, "rotate")) myRotation = val;
else if(!strcmp(variable, "min_frame_time")) minFrameTime = val;
else if(!strcmp(variable, "autolamp") && (lampVal != -1)) {
autoLamp = val;
if (autoLamp) {
if (streamCount > 0) setLamp(lampVal);
else setLamp(0);
} else {
setLamp(lampVal);
}
}
else if(!strcmp(variable, "lamp") && (lampVal != -1)) {
lampVal = constrain(val,0,100);
if (autoLamp) {
if (streamCount > 0) setLamp(lampVal);
else setLamp(0);
} else {
setLamp(lampVal);
}
}
else if(!strcmp(variable, "save_prefs")) {
if (filesystem) savePrefs(SPIFFS);
}
else if(!strcmp(variable, "clear_prefs")) {
if (filesystem) removePrefs(SPIFFS);
}
else if(!strcmp(variable, "reboot")) {
if (lampVal != -1) setLamp(0); // kill the lamp; otherwise it can remain on during the soft-reboot
esp_task_wdt_init(3,true); // schedule a a watchdog panic event for 3 seconds in the future
esp_task_wdt_add(NULL);
periph_module_disable(PERIPH_I2C0_MODULE); // try to shut I2C down properly
periph_module_disable(PERIPH_I2C1_MODULE);
periph_module_reset(PERIPH_I2C0_MODULE);
periph_module_reset(PERIPH_I2C1_MODULE);
Serial.print("REBOOT requested");
while(true) {
flashLED(50);
delay(150);
Serial.print('.');
}
}
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);
}
static esp_err_t status_handler(httpd_req_t *req){
static char json_response[1024];
char * p = json_response;
*p++ = '{';
// Do not get attempt to get sensor when in error; causes a panic..
if (critERR.length() == 0) {
sensor_t * s = esp_camera_sensor_get();
p+=sprintf(p, "\"lamp\":%d,", lampVal);
p+=sprintf(p, "\"autolamp\":%d,", autoLamp);
p+=sprintf(p, "\"min_frame_time\":%d,", minFrameTime);
p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
p+=sprintf(p, "\"quality\":%u,", s->status.quality);
p+=sprintf(p, "\"xclk\":%u,", xclk);
p+=sprintf(p, "\"brightness\":%d,", s->status.brightness);
p+=sprintf(p, "\"contrast\":%d,", s->status.contrast);
p+=sprintf(p, "\"saturation\":%d,", s->status.saturation);
p+=sprintf(p, "\"sharpness\":%d,", s->status.sharpness);
p+=sprintf(p, "\"special_effect\":%u,", s->status.special_effect);
p+=sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode);
p+=sprintf(p, "\"awb\":%u,", s->status.awb);
p+=sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain);
p+=sprintf(p, "\"aec\":%u,", s->status.aec);
p+=sprintf(p, "\"aec2\":%u,", s->status.aec2);
p+=sprintf(p, "\"ae_level\":%d,", s->status.ae_level);
p+=sprintf(p, "\"aec_value\":%u,", s->status.aec_value);
p+=sprintf(p, "\"agc\":%u,", s->status.agc);
p+=sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain);
p+=sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling);
p+=sprintf(p, "\"bpc\":%u,", s->status.bpc);
p+=sprintf(p, "\"wpc\":%u,", s->status.wpc);
p+=sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma);
p+=sprintf(p, "\"lenc\":%u,", s->status.lenc);
p+=sprintf(p, "\"vflip\":%u,", s->status.vflip);
p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar);
p+=sprintf(p, "\"cam_name\":\"%s\",", myName);
p+=sprintf(p, "\"code_ver\":\"%s\",", myVer);
p+=sprintf(p, "\"rotate\":\"%d\",", myRotation);
p+=sprintf(p, "\"stream_url\":\"%s\"", streamURL);
}
*p++ = '}';
*p++ = 0;
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, json_response, strlen(json_response));
}
static esp_err_t info_handler(httpd_req_t *req){
static char json_response[256];
char * p = json_response;
*p++ = '{';
p+=sprintf(p, "\"cam_name\":\"%s\",", myName);
p+=sprintf(p, "\"rotate\":\"%d\",", myRotation);
p+=sprintf(p, "\"stream_url\":\"%s\"", streamURL);
*p++ = '}';
*p++ = 0;
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, json_response, strlen(json_response));
}
static esp_err_t favicon_16x16_handler(httpd_req_t *req){
httpd_resp_set_type(req, "image/png");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)favicon_16x16_png, favicon_16x16_png_len);
}
static esp_err_t favicon_32x32_handler(httpd_req_t *req){
httpd_resp_set_type(req, "image/png");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)favicon_32x32_png, favicon_32x32_png_len);
}
static esp_err_t favicon_ico_handler(httpd_req_t *req){
httpd_resp_set_type(req, "image/x-icon");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)favicon_ico, favicon_ico_len);
}
static esp_err_t logo_svg_handler(httpd_req_t *req){
httpd_resp_set_type(req, "image/svg+xml");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)logo_svg, logo_svg_len);
}
static esp_err_t dump_handler(httpd_req_t *req){
flashLED(75);
Serial.println("\r\nDump requested via Web");
serialDump();
static char dumpOut[2000] = "";
char * d = dumpOut;
// Header
d+= sprintf(d,"<html><head><meta charset=\"utf-8\">\n");
d+= sprintf(d,"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n");
d+= sprintf(d,"<title>%s - Status</title>\n", myName);
d+= sprintf(d,"<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon-32x32.png\">\n");
d+= sprintf(d,"<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon-16x16.png\">\n");
d+= sprintf(d,"<link rel=\"stylesheet\" type=\"text/css\" href=\"/style.css\">\n");
d+= sprintf(d,"</head>\n");
d+= sprintf(d,"<body>\n");
d+= sprintf(d,"<img src=\"/logo.svg\" style=\"position: relative; float: right;\">\n");
if (critERR.length() > 0) {
d+= sprintf(d,"%s<hr>\n", critERR.c_str());
}
d+= sprintf(d,"<h1>ESP32 Cam Webserver</h1>\n");
// Module
d+= sprintf(d,"Name: %s<br>\n", myName);
d+= sprintf(d,"Firmware: %s (base: %s)<br>\n", myVer, baseVersion);
float sketchPct = 100 * sketchSize / sketchSpace;
d+= sprintf(d,"Sketch Size: %i (total: %i, %.1f%% used)<br>\n", sketchSize, sketchSpace, sketchPct);
d+= sprintf(d,"MD5: %s<br>\n", sketchMD5.c_str());
d+= sprintf(d,"ESP sdk: %s<br>\n", ESP.getSdkVersion());
// Network
d+= sprintf(d,"<h2>WiFi</h2>\n");
if (accesspoint) {
if (captivePortal) {
d+= sprintf(d,"Mode: AccessPoint with captive portal<br>\n");
} else {
d+= sprintf(d,"Mode: AccessPoint<br>\n");
}
d+= sprintf(d,"SSID: %s<br>\n", apName);
} else {
d+= sprintf(d,"Mode: Client<br>\n");
String ssidName = WiFi.SSID();
d+= sprintf(d,"SSID: %s<br>\n", ssidName.c_str());
d+= sprintf(d,"Rssi: %i<br>\n", WiFi.RSSI());
String bssid = WiFi.BSSIDstr();
d+= sprintf(d,"BSSID: %s<br>\n", bssid.c_str());
}
d+= sprintf(d,"IP address: %d.%d.%d.%d<br>\n", ip[0], ip[1], ip[2], ip[3]);
if (!accesspoint) {
d+= sprintf(d,"Netmask: %d.%d.%d.%d<br>\n", net[0], net[1], net[2], net[3]);
d+= sprintf(d,"Gateway: %d.%d.%d.%d<br>\n", gw[0], gw[1], gw[2], gw[3]);
}
d+= sprintf(d,"Http port: %i, Stream port: %i<br>\n", httpPort, streamPort);
byte mac[6];
WiFi.macAddress(mac);
d+= sprintf(d,"MAC: %02X:%02X:%02X:%02X:%02X:%02X<br>\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// System
d+= sprintf(d,"<h2>System</h2>\n");
if (haveTime) {
struct tm timeinfo;
if(getLocalTime(&timeinfo)){
char timeStringBuff[50]; //50 chars should be enough
strftime(timeStringBuff, sizeof(timeStringBuff), "%H:%M:%S, %A, %B %d %Y", &timeinfo);
//print like "const char*"
d+= sprintf(d,"Time: %s<br>\n", timeStringBuff);
}
}
int64_t sec = esp_timer_get_time() / 1000000;
int64_t upDays = int64_t(floor(sec/86400));
int upHours = int64_t(floor(sec/3600)) % 24;
int upMin = int64_t(floor(sec/60)) % 60;
int upSec = sec % 60;
int McuTc = (temprature_sens_read() - 32) / 1.8; // celsius
int McuTf = temprature_sens_read(); // fahrenheit
d+= sprintf(d,"Up: %" PRId64 ":%02i:%02i:%02i (d:h:m:s)<br>\n", upDays, upHours, upMin, upSec);
d+= sprintf(d,"Active streams: %i, Previous streams: %lu, Images captured: %lu<br>\n", streamCount, streamsServed, imagesServed);
d+= sprintf(d,"CPU Freq: %i MHz, Xclk Freq: %i MHz<br>\n", ESP.getCpuFreqMHz(), xclk);
d+= sprintf(d,"<span title=\"NOTE: Internal temperature sensor readings can be innacurate on the ESP32-c1 chipset, and may vary significantly between devices!\">");
d+= sprintf(d,"MCU temperature : %i &deg;C, %i &deg;F</span>\n<br>", McuTc, McuTf);
d+= sprintf(d,"Heap: %i, free: %i, min free: %i, max block: %i<br>\n", ESP.getHeapSize(), ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap());
if (psramFound()) {
d+= sprintf(d,"Psram: %i, free: %i, min free: %i, max block: %i<br>\n", ESP.getPsramSize(), ESP.getFreePsram(), ESP.getMinFreePsram(), ESP.getMaxAllocPsram());
} else {
d+= sprintf(d,"Psram: <span style=\"color:red;\">Not found</span>, please check your board configuration.<br>\n");
d+= sprintf(d,"- High resolution/quality images & streams will show incomplete frames due to low memory.<br>\n");
}
if (filesystem && (SPIFFS.totalBytes() > 0)) {
d+= sprintf(d,"Spiffs: %i, used: %i<br>\n", SPIFFS.totalBytes(), SPIFFS.usedBytes());
} else {
d+= sprintf(d,"Spiffs: <span style=\"color:red;\">No filesystem found</span>, please check your board configuration.<br>\n");
d+= sprintf(d,"- saving and restoring camera settings will not function without this.<br>\n");
}
// Footer
d+= sprintf(d,"<br><div class=\"input-group\">\n");
d+= sprintf(d,"<button title=\"Instant Refresh; the page reloads every minute anyway\" onclick=\"location.replace(document.URL)\">Refresh</button>\n");
d+= sprintf(d,"<button title=\"Force-stop all active streams on the camera module\" ");
d+= sprintf(d,"onclick=\"let throwaway = fetch('stop');setTimeout(function(){\nlocation.replace(document.URL);\n}, 200);\">Kill Stream</button>\n");
d+= sprintf(d,"<button title=\"Close this page\" onclick=\"javascript:window.close()\">Close</button>\n");
d+= sprintf(d,"</div>\n</body>\n");
// A javascript timer to refresh the page every minute.
d+= sprintf(d,"<script>\nsetTimeout(function(){\nlocation.replace(document.URL);\n}, 60000);\n");
d+= sprintf(d,"</script>\n</html>\n");
*d++ = 0;
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, dumpOut, strlen(dumpOut));
}
static esp_err_t stop_handler(httpd_req_t *req){
flashLED(75);
Serial.println("\r\nStream stop requested via Web");
streamKill = true;
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_send(req, NULL, 0);
}
static esp_err_t style_handler(httpd_req_t *req){
httpd_resp_set_type(req, "text/css");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)style_css, style_css_len);
}
static esp_err_t streamviewer_handler(httpd_req_t *req){
flashLED(75);
Serial.println("Stream viewer requested");
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)streamviewer_html, streamviewer_html_len);
}
static esp_err_t error_handler(httpd_req_t *req){
flashLED(75);
Serial.println("Sending error page");
std::string s(error_html);
size_t index;
while ((index = s.find("<APPURL>")) != std::string::npos)
s.replace(index, strlen("<APPURL>"), httpURL);
while ((index = s.find("<CAMNAME>")) != std::string::npos)
s.replace(index, strlen("<CAMNAME>"), myName);
while ((index = s.find("<ERRORTEXT>")) != std::string::npos)
s.replace(index, strlen("<ERRORTEXT>"), critERR.c_str());
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)s.c_str(), s.length());
}
static esp_err_t index_handler(httpd_req_t *req){
char* buf;
size_t buf_len;
char view[32] = {0,};
flashLED(75);
// See if we have a specific target (full/simple/portal) and serve as appropriate
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, "view", view, sizeof(view)) == 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 {
// no target specified; default.
strcpy(view,default_index);
// If captive portal is active send that instead
if (captivePortal) {
strcpy(view,"portal");
}
}
if (strncmp(view,"simple", sizeof(view)) == 0) {
Serial.println("Simple index page requested");
if (critERR.length() > 0) return error_handler(req);
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)index_simple_html, index_simple_html_len);
} else if(strncmp(view,"full", sizeof(view)) == 0) {
Serial.println("Full index page requested");
if (critERR.length() > 0) return error_handler(req);
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
if (sensorPID == OV3660_PID) {
return httpd_resp_send(req, (const char *)index_ov3660_html, index_ov3660_html_len);
}
return httpd_resp_send(req, (const char *)index_ov2640_html, index_ov2640_html_len);
} else if(strncmp(view,"portal", sizeof(view)) == 0) {
//Prototype captive portal landing page.
Serial.println("Portal page requested");
std::string s(portal_html);
size_t index;
while ((index = s.find("<APPURL>")) != std::string::npos)
s.replace(index, strlen("<APPURL>"), httpURL);
while ((index = s.find("<STREAMURL>")) != std::string::npos)
s.replace(index, strlen("<STREAMURL>"), streamURL);
while ((index = s.find("<CAMNAME>")) != std::string::npos)
s.replace(index, strlen("<CAMNAME>"), myName);
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Content-Encoding", "identity");
return httpd_resp_send(req, (const char *)s.c_str(), s.length());
} else {
Serial.print("Unknown page requested: ");
Serial.println(view);
httpd_resp_send_404(req);
return ESP_FAIL;
}
}
void startCameraServer(int hPort, int sPort){
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 16; // we use more than the default 8 (on port 80)
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_uri_t status_uri = {
.uri = "/status",
.method = HTTP_GET,
.handler = status_handler,
.user_ctx = NULL
};
httpd_uri_t cmd_uri = {
.uri = "/control",
.method = HTTP_GET,
.handler = cmd_handler,
.user_ctx = NULL
};
httpd_uri_t capture_uri = {
.uri = "/capture",
.method = HTTP_GET,
.handler = capture_handler,
.user_ctx = NULL
};
httpd_uri_t style_uri = {
.uri = "/style.css",
.method = HTTP_GET,
.handler = style_handler,
.user_ctx = NULL
};
httpd_uri_t favicon_16x16_uri = {
.uri = "/favicon-16x16.png",
.method = HTTP_GET,
.handler = favicon_16x16_handler,
.user_ctx = NULL
};
httpd_uri_t favicon_32x32_uri = {
.uri = "/favicon-32x32.png",
.method = HTTP_GET,
.handler = favicon_32x32_handler,
.user_ctx = NULL
};
httpd_uri_t favicon_ico_uri = {
.uri = "/favicon.ico",
.method = HTTP_GET,
.handler = favicon_ico_handler,
.user_ctx = NULL
};
httpd_uri_t logo_svg_uri = {
.uri = "/logo.svg",
.method = HTTP_GET,
.handler = logo_svg_handler,
.user_ctx = NULL
};
httpd_uri_t dump_uri = {
.uri = "/dump",
.method = HTTP_GET,
.handler = dump_handler,
.user_ctx = NULL
};
httpd_uri_t stop_uri = {
.uri = "/stop",
.method = HTTP_GET,
.handler = stop_handler,
.user_ctx = NULL
};
httpd_uri_t stream_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = stream_handler,
.user_ctx = NULL
};
httpd_uri_t streamviewer_uri = {
.uri = "/view",
.method = HTTP_GET,
.handler = streamviewer_handler,
.user_ctx = NULL
};
httpd_uri_t info_uri = {
.uri = "/info",
.method = HTTP_GET,
.handler = info_handler,
.user_ctx = NULL
};
httpd_uri_t error_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = error_handler,
.user_ctx = NULL
};
httpd_uri_t viewerror_uri = {
.uri = "/view",
.method = HTTP_GET,
.handler = error_handler,
.user_ctx = NULL
};
// Request Handlers; config.max_uri_handlers (above) must be >= the number of handlers
config.server_port = hPort;
config.ctrl_port = hPort;
Serial.printf("Starting web server on port: '%d'\r\n", config.server_port);
if (httpd_start(&camera_httpd, &config) == ESP_OK) {
if (critERR.length() > 0) {
httpd_register_uri_handler(camera_httpd, &error_uri);
} else {
httpd_register_uri_handler(camera_httpd, &index_uri);
httpd_register_uri_handler(camera_httpd, &cmd_uri);
httpd_register_uri_handler(camera_httpd, &status_uri);
httpd_register_uri_handler(camera_httpd, &capture_uri);
}
httpd_register_uri_handler(camera_httpd, &style_uri);
httpd_register_uri_handler(camera_httpd, &favicon_16x16_uri);
httpd_register_uri_handler(camera_httpd, &favicon_32x32_uri);
httpd_register_uri_handler(camera_httpd, &favicon_ico_uri);
httpd_register_uri_handler(camera_httpd, &logo_svg_uri);
httpd_register_uri_handler(camera_httpd, &dump_uri);
httpd_register_uri_handler(camera_httpd, &stop_uri);
}
config.server_port = sPort;
config.ctrl_port = sPort;
Serial.printf("Starting stream server on port: '%d'\r\n", config.server_port);
if (httpd_start(&stream_httpd, &config) == ESP_OK) {
if (critERR.length() > 0) {
httpd_register_uri_handler(camera_httpd, &error_uri);
httpd_register_uri_handler(camera_httpd, &viewerror_uri);
} else {
httpd_register_uri_handler(stream_httpd, &stream_uri);
httpd_register_uri_handler(stream_httpd, &info_uri);
httpd_register_uri_handler(stream_httpd, &streamviewer_uri);
}
httpd_register_uri_handler(stream_httpd, &favicon_16x16_uri);
httpd_register_uri_handler(stream_httpd, &favicon_32x32_uri);
httpd_register_uri_handler(stream_httpd, &favicon_ico_uri);
}
}

244
ESP32_CAM_NEW/camera_pins.h Normal file
View File

@@ -0,0 +1,244 @@
/*
* Pin definitions for some common ESP-CAM modules
*
* Select the module to use in myconfig.h
* Defaults to AI-THINKER CAM module
*
*/
#if defined(CAMERA_MODEL_AI_THINKER)
//
// AI Thinker
// https://github.com/SeeedDocument/forum_doc/raw/master/reg/ESP32_CAM_V1.6.pdf
//
#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
#define LED_PIN 33 // Status led
#define LED_ON LOW // - Pin is inverted.
#define LED_OFF HIGH //
#define LAMP_PIN 4 // LED FloodLamp.
#elif defined(CAMERA_MODEL_WROVER_KIT)
//
// ESP WROVER
// https://dl.espressif.com/dl/schematics/ESP-WROVER-KIT_SCH-2.pdf
//
#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
#define LED_PIN 2 // A status led on the RGB; could also use pin 0 or 4
#define LED_ON HIGH //
#define LED_OFF LOW //
// #define LAMP_PIN x // No LED FloodLamp.
#elif defined(CAMERA_MODEL_ESP_EYE)
//
// ESP-EYE
// https://twitter.com/esp32net/status/1085488403460882437
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 4
#define SIOD_GPIO_NUM 18
#define SIOC_GPIO_NUM 23
#define Y9_GPIO_NUM 36
#define Y8_GPIO_NUM 37
#define Y7_GPIO_NUM 38
#define Y6_GPIO_NUM 39
#define Y5_GPIO_NUM 35
#define Y4_GPIO_NUM 14
#define Y3_GPIO_NUM 13
#define Y2_GPIO_NUM 34
#define VSYNC_GPIO_NUM 5
#define HREF_GPIO_NUM 27
#define PCLK_GPIO_NUM 25
#define LED_PIN 21 // Status led
#define LED_ON HIGH //
#define LED_OFF LOW //
// #define LAMP_PIN x // No LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
//
// ESP32 M5STACK
//
#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
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM)
//
// ESP32 M5STACK V2
//
#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
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_WIDE)
//
// ESP32 M5STACK WIDE
//
#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
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_M5STACK_ESP32CAM)
//
// Common M5 Stack 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
// Note NO PSRAM,; so maximum working resolution is XGA 1024×768
// M5 Stack status/illumination LED details unknown/unclear
// #define LED_PIN x // Status led
// #define LED_ON HIGH //
// #define LED_OFF LOW //
// #define LAMP_PIN x // LED FloodLamp.
#elif defined(CAMERA_MODEL_TTGO_T_JOURNAL)
//
// LilyGO TTGO T-Journal ESP32; with OLED! but not used here.. :-(
#define PWDN_GPIO_NUM 0
#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
// TTGO T Journal status/illumination LED details unknown/unclear
// #define LED_PIN 33 // Status led
// #define LED_ON LOW // - Pin is inverted.
// #define LED_OFF HIGH //
// #define LAMP_PIN 4 // LED FloodLamp.
#elif defined(CAMERA_MODEL_ARDUCAM_ESP32S_UNO)
// Pins from user @rdragonrydr
// https://github.com/ArduCAM/ArduCAM_ESP32S_UNO/
// Based on AI-THINKER definitions
#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
#define LED_PIN 2 // Status led
#define LED_ON HIGH // - Pin is not inverted.
#define LED_OFF LOW //
//#define LAMP_PIN x // No LED FloodLamp.
#else
// Well.
// that went badly...
#error "Camera model not selected, did you forget to uncomment it in myconfig?"
#endif

367
ESP32_CAM_NEW/css.h Normal file
View File

@@ -0,0 +1,367 @@
/*
* Master CSS file for the camera pages
*/
const uint8_t style_css[] = R"=====(/*
* CSS for the esp32 cam webserver
*/
body {
font-family: Arial,Helvetica,sans-serif;
background: #181818;
color: #EFEFEF;
font-size: 16px
}
a {
color: #EFEFEF;
text-decoration: underline
}
h2 {
font-size: 18px
}
section.main {
display: flex
}
#menu,section.main {
flex-direction: column
}
#menu {
display: none;
flex-wrap: nowrap;
color: #EFEFEF;
width: 380px;
background: #363636;
padding: 8px;
border-radius: 4px;
margin-top: -10px;
margin-right: 10px;
}
/* #content {
display: flex;
flex-wrap: wrap;
align-items: stretch
}
*/
figure {
padding: 0px;
margin: 0;
margin-block-start: 0;
margin-block-end: 0;
margin-inline-start: 0;
margin-inline-end: 0
}
figure img {
display: block;
max-width: 100%;
width: auto;
height: auto;
border-radius: 4px;
margin-top: 8px;
}
section#buttons {
display: flex;
flex-wrap: nowrap;
justify-content: space-between
}
#nav-toggle {
cursor: pointer;
display: block
}
#nav-toggle-cb {
outline: 0;
opacity: 0;
width: 0;
height: 0
}
#nav-toggle-cb:checked+#menu {
display: flex
}
#quality {
transform: rotateY(180deg);
}
.input-group {
display: flex;
flex-wrap: nowrap;
line-height: 22px;
margin: 5px 0
}
.input-group>label {
display: inline-block;
padding-right: 10px;
min-width: 47%
}
.input-group input,.input-group select {
flex-grow: 1
}
.range-max,.range-min {
display: inline-block;
padding: 0 5px
}
button {
display: block;
margin: 3px;
padding: 0 8px;
border: 0;
line-height: 28px;
cursor: pointer;
color: #fff;
background: #ff3034;
border-radius: 5px;
font-size: 16px;
outline: 0
}
button:hover {
background: #ff494d
}
button:active {
background: #f21c21
}
button.disabled {
cursor: default;
background: #a0a0a0
}
input[type=range] {
-webkit-appearance: none;
width: 0;
height: 22px;
background: #363636;
cursor: pointer;
margin: 0
}
input[type=range]:focus {
outline: 0
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 2px;
cursor: pointer;
background: #EFEFEF;
border-radius: 0;
border: 0 solid #EFEFEF
}
input[type=range]::-webkit-slider-thumb {
border: 1px solid rgba(0,0,30,0);
height: 22px;
width: 22px;
border-radius: 50px;
background: #ff3034;
cursor: pointer;
-webkit-appearance: none;
margin-top: -11.5px
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #EFEFEF
}
input[type=range]::-moz-range-track {
width: 100%;
height: 2px;
cursor: pointer;
background: #EFEFEF;
border-radius: 0;
border: 0 solid #EFEFEF
}
input[type=range]::-moz-range-thumb {
border: 1px solid rgba(0,0,30,0);
height: 22px;
width: 22px;
border-radius: 50px;
background: #ff3034;
cursor: pointer
}
input[type=range]::-ms-track {
width: 100%;
height: 2px;
cursor: pointer;
background: 0 0;
border-color: transparent;
color: transparent
}
input[type=range]::-ms-fill-lower {
background: #EFEFEF;
border: 0 solid #EFEFEF;
border-radius: 0
}
input[type=range]::-ms-fill-upper {
background: #EFEFEF;
border: 0 solid #EFEFEF;
border-radius: 0
}
input[type=range]::-ms-thumb {
border: 1px solid rgba(0,0,30,0);
height: 22px;
width: 22px;
border-radius: 50px;
background: #ff3034;
cursor: pointer;
height: 2px
}
input[type=range]:focus::-ms-fill-lower {
background: #EFEFEF
}
input[type=range]:focus::-ms-fill-upper {
background: #363636
}
input[type=text] {
border: 1px solid #363636;
font-size: 14px;
height: 20px;
margin: 1px;
outline: 0;
border-radius: 5px
}
.switch {
display: block;
position: relative;
line-height: 22px;
font-size: 16px;
height: 22px
}
.switch input {
outline: 0;
opacity: 0;
width: 0;
height: 0
}
.slider {
width: 50px;
height: 22px;
border-radius: 22px;
cursor: pointer;
background-color: grey
}
.slider,.slider:before {
display: inline-block;
transition: .4s
}
.slider:before {
position: relative;
content: "";
border-radius: 50%;
height: 16px;
width: 16px;
left: 4px;
top: 3px;
background-color: #fff
}
input:checked+.slider {
background-color: #ff3034
}
input:checked+.slider:before {
-webkit-transform: translateX(26px);
transform: translateX(26px)
}
select {
border: 1px solid #363636;
font-size: 14px;
height: 22px;
outline: 0;
border-radius: 5px
}
.image-container {
position: relative;
min-width: 160px;
transform-origin: top left
}
.close {
position: absolute;
z-index: 99;
background: #ff3034;
width: 16px;
height: 16px;
border-radius: 100px;
color: #fff;
text-align: center;
line-height: 18px;
cursor: pointer
}
.close-rot-none {
left: 5px;
top: 5px;
}
.close-rot-left {
right: 5px;
top: 5px;
}
.close-rot-right {
left: 5px;
bottom: 5px;
}
.hidden {
display: none
}
.inline-button {
line-height: 20px;
margin: 2px;
padding: 1px 4px 2px 4px;
}
.loader {
border: 0.5em solid #f3f3f3; /* Light grey */
border-top: 0.5em solid #000000; /* white */
border-radius: 50%;
width: 1em;
height: 1em;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
@-webkit-keyframes spin { /* Safari */
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
})=====";
size_t style_css_len = sizeof(style_css)-1;

515
ESP32_CAM_NEW/index_other.h Normal file
View File

@@ -0,0 +1,515 @@
/*
* simpleviewer and streamviewer
*/
const uint8_t index_simple_html[] = R"=====(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title id="title">ESP32-CAM Simplified View</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
@media (min-width: 800px) and (orientation:landscape) {
#content {
display:flex;
flex-wrap: nowrap;
flex-direction: column;
align-items: flex-start;
}
}
</style>
</head>
<body>
<section class="main">
<div id="logo">
<label for="nav-toggle-cb" id="nav-toggle" style="float:left;" title="Settings">&#9776;&nbsp;</label>
<button id="swap-viewer" style="float:left;" title="Swap to full feature viewer">Full</button>
<button id="get-still" style="float:left;">Get Still</button>
<button id="toggle-stream" style="float:left;" class="hidden">Start Stream</button>
<div id="wait-settings" style="float:left;" class="loader" title="Waiting for camera settings to load"></div>
</div>
<div id="content">
<div class="hidden" id="sidebar">
<input type="checkbox" id="nav-toggle-cb">
<nav id="menu" style="width:24em;">
<div class="input-group hidden" id="lamp-group" title="Flashlight LED.&#013;&#013;Warning:&#013;Built-In lamps can be Very Bright! Avoid looking directly at LED&#013;Can draw a lot of power and may cause visual artifacts, affect WiFi or even brownout the camera on high settings">
<label for="lamp">Light</label>
<div class="range-min">Off</div>
<input type="range" id="lamp" min="0" max="100" value="0" class="action-setting">
<div class="range-max">Full&#9888;</div>
</div>
<div class="input-group" id="framesize-group">
<label for="framesize">Resolution</label>
<select id="framesize" class="action-setting">
<option value="13">UXGA (1600x1200)</option>
<option value="12">SXGA (1280x1024)</option>
<option value="11">HD (1280x720)</option>
<option value="10">XGA (1024x768)</option>
<option value="9">SVGA (800x600)</option>
<option value="8">VGA (640x480)</option>
<option value="7">HVGA (480x320)</option>
<option value="6">CIF (400x296)</option>
<option value="5">QVGA (320x240)</option>
<option value="3">HQVGA (240x176)</option>
<option value="1">QQVGA (160x120)</option>
<option value="0">THUMB (96x96)</option>
</select>
</div>
<!-- Hide the next entries, they are present in the body so that we
can pass settings to/from them for use in the scripting, not for user setting -->
<div id="rotate" class="action-setting hidden"></div>
<div id="cam_name" class="action-setting hidden"></div>
<div id="stream_url" class="action-setting hidden"></div>
</nav>
</div>
<figure>
<div id="stream-container" class="image-container hidden">
<div class="close close-rot-none" id="close-stream">×</div>
<img id="stream" src="">
</div>
</figure>
</div>
</section>
</body>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
var baseHost = document.location.origin;
var streamURL = 'Undefined';
const settings = document.getElementById('sidebar')
const waitSettings = document.getElementById('wait-settings')
const lampGroup = document.getElementById('lamp-group')
const rotate = document.getElementById('rotate')
const view = document.getElementById('stream')
const viewContainer = document.getElementById('stream-container')
const stillButton = document.getElementById('get-still')
const streamButton = document.getElementById('toggle-stream')
const closeButton = document.getElementById('close-stream')
const swapButton = document.getElementById('swap-viewer')
const hide = el => {
el.classList.add('hidden')
}
const show = el => {
el.classList.remove('hidden')
}
const disable = el => {
el.classList.add('disabled')
el.disabled = true
}
const enable = el => {
el.classList.remove('disabled')
el.disabled = false
}
const updateValue = (el, value, updateRemote) => {
updateRemote = updateRemote == null ? true : updateRemote
let initialValue
if (el.type === 'checkbox') {
initialValue = el.checked
value = !!value
el.checked = value
} else {
initialValue = el.value
el.value = value
}
if (updateRemote && initialValue !== value) {
updateConfig(el);
} else if(!updateRemote){
if(el.id === "lamp"){
if (value == -1) {
hide(lampGroup)
} else {
show(lampGroup)
}
} else if(el.id === "cam_name"){
window.document.title = value;
console.log('Name set to: ' + value);
} else if(el.id === "code_ver"){
console.log('Firmware Build: ' + value);
} else if(el.id === "rotate"){
rotate.value = value;
applyRotation();
} else if(el.id === "stream_url"){
streamURL = value;
streamButton.setAttribute("title", `Start the stream :: {streamURL}`);
console.log('Stream URL set to:' + value);
}
}
}
var rangeUpdateScheduled = false
var latestRangeConfig
function updateRangeConfig (el) {
latestRangeConfig = el
if (!rangeUpdateScheduled) {
rangeUpdateScheduled = true;
setTimeout(function(){
rangeUpdateScheduled = false
updateConfig(latestRangeConfig)
}, 150);
}
}
function updateConfig (el) {
let value
switch (el.type) {
case 'checkbox':
value = el.checked ? 1 : 0
break
case 'range':
case 'select-one':
value = el.value
break
case 'button':
case 'submit':
value = '1'
break
default:
return
}
const query = `${baseHost}/control?var=${el.id}&val=${value}`
fetch(query)
.then(response => {
console.log(`request to ${query} finished, status: ${response.status}`)
})
}
document
.querySelectorAll('.close')
.forEach(el => {
el.onclick = () => {
hide(el.parentNode)
}
})
// read initial values
fetch(`${baseHost}/status`)
.then(function (response) {
return response.json()
})
.then(function (state) {
document
.querySelectorAll('.action-setting')
.forEach(el => {
updateValue(el, state[el.id], false)
})
hide(waitSettings);
show(settings);
show(streamButton);
startStream();
})
// Put some helpful text on the 'Still' button
stillButton.setAttribute("title", `Capture a still image :: ${baseHost}/capture`);
const stopStream = () => {
window.stop();
streamButton.innerHTML = 'Start Stream';
streamButton.setAttribute("title", `Start the stream :: ${streamURL}`);
hide(viewContainer);
}
const startStream = () => {
view.src = streamURL;
view.scrollIntoView(false);
streamButton.innerHTML = 'Stop Stream';
streamButton.setAttribute("title", `Stop the stream`);
show(viewContainer);
}
const applyRotation = () => {
rot = rotate.value;
if (rot == -90) {
viewContainer.style.transform = `rotate(-90deg) translate(-100%)`;
closeButton.classList.remove('close-rot-none');
closeButton.classList.remove('close-rot-right');
closeButton.classList.add('close-rot-left');
} else if (rot == 90) {
viewContainer.style.transform = `rotate(90deg) translate(0, -100%)`;
closeButton.classList.remove('close-rot-left');
closeButton.classList.remove('close-rot-none');
closeButton.classList.add('close-rot-right');
} else {
viewContainer.style.transform = `rotate(0deg)`;
closeButton.classList.remove('close-rot-left');
closeButton.classList.remove('close-rot-right');
closeButton.classList.add('close-rot-none');
}
console.log('Rotation ' + rot + ' applied');
}
// Attach actions to controls
stillButton.onclick = () => {
stopStream();
view.src = `${baseHost}/capture?_cb=${Date.now()}`;
view.scrollIntoView(false);
show(viewContainer);
}
closeButton.onclick = () => {
stopStream();
hide(viewContainer);
}
streamButton.onclick = () => {
const streamEnabled = streamButton.innerHTML === 'Stop Stream'
if (streamEnabled) {
stopStream();
} else {
startStream();
}
}
// Attach default on change action
document
.querySelectorAll('.action-setting')
.forEach(el => {
el.onchange = () => updateConfig(el)
})
// Update range sliders as they are being moved
document
.querySelectorAll('input[type="range"]')
.forEach(el => {
el.oninput = () => updateRangeConfig(el)
})
// Custom actions
// Detection and framesize
rotate.onchange = () => {
applyRotation();
updateConfig(rotate);
}
framesize.onchange = () => {
updateConfig(framesize)
}
swapButton.onclick = () => {
window.open('/?view=full','_self');
}
})
</script>
</html>)=====";
size_t index_simple_html_len = sizeof(index_simple_html)-1;
/* Stream Viewer */
const uint8_t streamviewer_html[] = R"=====(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title id="title">ESP32-CAM StreamViewer</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<style>
/* No stylesheet, define all style elements here */
body {
font-family: Arial,Helvetica,sans-serif;
background: #181818;
color: #EFEFEF;
font-size: 16px;
margin: 0px;
overflow:hidden;
}
img {
object-fit: contain;
display: block;
margin: 0px;
padding: 0px;
width: 100vw;
height: 100vh;
}
.loader {
border: 0.5em solid #f3f3f3;
border-top: 0.5em solid #000000;
border-radius: 50%;
width: 1em;
height: 1em;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
@-webkit-keyframes spin { /* Safari */
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<section class="main">
<div id="wait-settings" style="float:left;" class="loader" title="Waiting for stream settings to load"></div>
<div style="display: none;">
<!-- Hide the next entries, they are present in the body so that we
can pass settings to/from them for use in the scripting -->
<div id="rotate" class="action-setting hidden">0</div>
<div id="cam_name" class="action-setting hidden"></div>
<div id="stream_url" class="action-setting hidden"></div>
</div>
<img id="stream" src="">
</section>
</body>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
var baseHost = document.location.origin;
var streamURL = 'Undefined';
const rotate = document.getElementById('rotate')
const stream = document.getElementById('stream')
const spinner = document.getElementById('wait-settings')
const updateValue = (el, value, updateRemote) => {
updateRemote = updateRemote == null ? true : updateRemote
let initialValue
if (el.type === 'checkbox') {
initialValue = el.checked
value = !!value
el.checked = value
} else {
initialValue = el.value
el.value = value
}
if (updateRemote && initialValue !== value) {
updateConfig(el);
} else if(!updateRemote){
if(el.id === "cam_name"){
window.document.title = value;
stream.setAttribute("title", value + "\n(doubleclick for fullscreen)");
console.log('Name set to: ' + value);
} else if(el.id === "rotate"){
rotate.value = value;
console.log('Rotate recieved: ' + rotate.value);
} else if(el.id === "stream_url"){
streamURL = value;
console.log('Stream URL set to:' + value);
}
}
}
// read initial values
fetch(`${baseHost}/info`)
.then(function (response) {
return response.json()
})
.then(function (state) {
document
.querySelectorAll('.action-setting')
.forEach(el => {
updateValue(el, state[el.id], false)
})
spinner.style.display = `none`;
applyRotation();
startStream();
})
const startStream = () => {
stream.src = streamURL;
stream.style.display = `block`;
}
const applyRotation = () => {
rot = rotate.value;
if (rot == -90) {
stream.style.transform = `rotate(-90deg)`;
} else if (rot == 90) {
stream.style.transform = `rotate(90deg)`;
}
console.log('Rotation ' + rot + ' applied');
}
stream.ondblclick = () => {
if (stream.requestFullscreen) {
stream.requestFullscreen();
} else if (stream.mozRequestFullScreen) { /* Firefox */
stream.mozRequestFullScreen();
} else if (stream.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
stream.webkitRequestFullscreen();
} else if (stream.msRequestFullscreen) { /* IE/Edge */
stream.msRequestFullscreen();
}
}
})
</script>
</html>)=====";
size_t streamviewer_html_len = sizeof(streamviewer_html)-1;
/* Captive Portal page
we replace the <> delimited strings with correct values as it is served */
const std::string portal_html = R"=====(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title id="title"><CAMNAME> - portal</title>
<link rel="icon" type="image/png" sizes="32x32" href="<APPURL>favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="<APPURL>favicon-16x16.png">
<link rel="stylesheet" type="text/css" href="<APPURL>style.css">
</head>
<body style="text-align: center;">
<img src="<APPURL>logo.svg" style="position: relative; float: right;">
<h1><CAMNAME> - access portal</h1>
<div class="input-group" style="margin: auto; width: max-content;">
<a href="<APPURL>?view=simple" title="Click here for a simple view with minimum control" style="text-decoration: none;" target="_blank">
<button>Simple Viewer</button></a>
<a href="<APPURL>?view=full" title="Click here for the main camera page with full controls" style="text-decoration: none;" target="_blank">
<button>Full Viewer</button></a>
<a href="<STREAMURL>view" title="Click here for the dedicated stream viewer" style="text-decoration: none;" target="_blank">
<button>Stream Viewer</button></a>
</div>
<hr>
<a href="<APPURL>dump" title="Information dump page" target="_blank">Camera Details</a><br>
</body>
</html>)=====";
/* Error page
we replace the <> delimited strings with correct values as it is served */
const std::string error_html = R"=====(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title id="title"><CAMNAME> - Error</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="ico\" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="stylesheet" type="text/css" href="<APPURL>style.css">
</head>
<body style="text-align: center;">
<img src="<APPURL>logo.svg" style="position: relative; float: right;">
<h1><CAMNAME></h1>
<ERRORTEXT>
</body>
<script>
setTimeout(function(){
location.replace(document.URL);
}, 60000);
</script>
</html>)=====";

View File

@@ -0,0 +1,633 @@
/*
* primary HTML for the OV2640 camera module
*/
const uint8_t index_ov2640_html[] = R"=====(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 OV2640</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
@media (min-width: 800px) and (orientation:landscape) {
#content {
display:flex;
flex-wrap: nowrap;
align-items: stretch
}
}
</style>
</head>
<body>
<section class="main">
<div id="logo">
<label for="nav-toggle-cb" id="nav-toggle" style="float:left;">&#9776;&nbsp;&nbsp;Settings&nbsp;&nbsp;&nbsp;&nbsp;</label>
<button id="swap-viewer" style="float:left;" title="Swap to simple viewer">Simple</button>
<button id="get-still" style="float:left;">Get Still</button>
<button id="toggle-stream" style="float:left;" class="hidden">Start Stream</button>
<div id="wait-settings" style="float:left;" class="loader" title="Waiting for camera settings to load"></div>
</div>
<div id="content">
<div class="hidden" id="sidebar">
<input type="checkbox" id="nav-toggle-cb" checked="checked">
<nav id="menu">
<div class="input-group hidden" id="lamp-group" title="Flashlight LED.&#013;&#013;Warning:&#013;Built-In lamps can be Very Bright! Avoid looking directly at LED&#013;Can draw a lot of power and may cause visual artifacts, affect WiFi or even brownout the camera on high settings">
<label for="lamp">Light</label>
<div class="range-min">Off</div>
<input type="range" id="lamp" min="0" max="100" value="0" class="default-action">
<div class="range-max"><span style="font-size: 125%;">&#9888;</span>Full</div>
</div>
<div class="input-group hidden" id="autolamp-group" title="When enabled the lamp will only turn on while the camera is active">
<label for="autolamp">Auto Lamp</label>
<div class="switch">
<input id="autolamp" type="checkbox" class="default-action">
<label class="slider" for="autolamp"></label>
</div>
</div>
<div class="input-group" id="framesize-group" title="Camera resolution&#013;Higher resolutions will result in lower framerates">
<label for="framesize">Resolution</label>
<select id="framesize" class="default-action">
<option value="13">UXGA (1600x1200)</option>
<option value="12">SXGA (1280x1024)</option>
<option value="11">HD (1280x720)</option>
<option value="10">XGA (1024x768)</option>
<option value="9">SVGA (800x600)</option>
<option value="8">VGA (640x480)</option>
<option value="7">HVGA (480x320)</option>
<option value="6">CIF (400x296)</option>
<option value="5">QVGA (320x240)</option>
<option value="3">HQVGA (240x176)</option>
<option value="1">QQVGA (160x120)</option>
<option value="0">THUMB (96x96)</option>
</select>
</div>
<div class="input-group" id="quality-group" title="Camera Image and Stream quality factor&#013;Higher settings will result in lower framerates">
<label for="quality">Quality</label>
<div class="range-min">Low</div>
<!-- Note; the following element is 'flipped' in CSS so that it slides from High to Low
As a result the 'min' and 'max' values are reversed here too -->
<input type="range" id="quality" min="6" max="63" value="10" class="default-action">
<div class="range-max">High</div>
</div>
<div class="input-group" id="set-xclk-group" title="Camera Bus Clock Frequency&#013;Increasing this will raise the camera framerate and capture speed&#013;&#013;Raising too far will result in visual artifacts and/or incomplete frames&#013;This setting can vary a lot between boards, budget boards typically need lower values">
<label for="set-xclk">XCLK</label>
<div class="text">
<input id="xclk" type="number" min="2" max="32" size="3" step="1" class="default-action">
<div class="range-max">MHz</div>
</div>
</div>
<div class="input-group" id="brightness-group">
<label for="brightness">Brightness</label>
<div class="range-min">-2</div>
<input type="range" id="brightness" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="contrast-group">
<label for="contrast">Contrast</label>
<div class="range-min">-2</div>
<input type="range" id="contrast" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="saturation-group">
<label for="saturation">Saturation</label>
<div class="range-min">-2</div>
<input type="range" id="saturation" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="special_effect-group">
<label for="special_effect">Special Effect</label>
<select id="special_effect" class="default-action">
<option value="0" selected="selected">No Effect</option>
<option value="1">Negative</option>
<option value="2">Grayscale</option>
<option value="3">Red Tint</option>
<option value="4">Green Tint</option>
<option value="5">Blue Tint</option>
<option value="6">Sepia</option>
</select>
</div>
<div class="input-group" id="awb-group">
<label for="awb">AWB Enable</label>
<div class="switch">
<input id="awb" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="awb"></label>
</div>
</div>
<div class="input-group" id="awb_gain-group">
<label for="awb_gain">Manual AWB Gain</label>
<div class="switch">
<input id="awb_gain" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="awb_gain"></label>
</div>
</div>
<div class="input-group" id="wb_mode-group">
<label for="wb_mode">WB Mode</label>
<select id="wb_mode" class="default-action">
<option value="0" selected="selected">Auto</option>
<option value="1">Sunny</option>
<option value="2">Cloudy</option>
<option value="3">Office</option>
<option value="4">Home</option>
</select>
</div>
<div class="input-group" id="aec-group">
<label for="aec">AEC Sensor Enable</label>
<div class="switch">
<input id="aec" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="aec"></label>
</div>
</div>
<div class="input-group" id="aec2-group">
<label for="aec2">AEC DSP</label>
<div class="switch">
<input id="aec2" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="aec2"></label>
</div>
</div>
<div class="input-group" id="ae_level-group">
<label for="ae_level">AE Level</label>
<div class="range-min">-2</div>
<input type="range" id="ae_level" min="-2" max="2" value="0" class="default-action">
<div class="range-max">2</div>
</div>
<div class="input-group" id="aec_value-group">
<label for="aec_value">Exposure</label>
<div class="range-min">0</div>
<input type="range" id="aec_value" min="0" max="1200" value="204" class="default-action">
<div class="range-max">1200</div>
</div>
<div class="input-group" id="agc-group">
<label for="agc">AGC</label>
<div class="switch">
<input id="agc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="agc"></label>
</div>
</div>
<div class="input-group hidden" id="agc_gain-group">
<label for="agc_gain">Gain</label>
<div class="range-min">1x</div>
<input type="range" id="agc_gain" min="0" max="30" value="5" class="default-action">
<div class="range-max">31x</div>
</div>
<div class="input-group" id="gainceiling-group">
<label for="gainceiling">Gain Ceiling</label>
<div class="range-min">2x</div>
<input type="range" id="gainceiling" min="0" max="6" value="0" class="default-action">
<div class="range-max">128x</div>
</div>
<div class="input-group" id="bpc-group">
<label for="bpc">BPC</label>
<div class="switch">
<input id="bpc" type="checkbox" class="default-action">
<label class="slider" for="bpc"></label>
</div>
</div>
<div class="input-group" id="wpc-group">
<label for="wpc">WPC</label>
<div class="switch">
<input id="wpc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="wpc"></label>
</div>
</div>
<div class="input-group" id="raw_gma-group">
<label for="raw_gma">Raw GMA Enable</label>
<div class="switch">
<input id="raw_gma" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="raw_gma"></label>
</div>
</div>
<div class="input-group" id="lenc-group">
<label for="lenc">Lens Correction</label>
<div class="switch">
<input id="lenc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="lenc"></label>
</div>
</div>
<div class="input-group" id="hmirror-group">
<label for="hmirror">H-Mirror Stream</label>
<div class="switch">
<input id="hmirror" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="hmirror"></label>
</div>
</div>
<div class="input-group" id="vflip-group">
<label for="vflip">V-Flip Stream</label>
<div class="switch">
<input id="vflip" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="vflip"></label>
</div>
</div>
<div class="input-group" id="rotate-group">
<label for="rotate">Rotate in Browser</label>
<select id="rotate" class="default-action">
<option value="90">90&deg; (Right)</option>
<option value="0" selected="selected">0&deg; (None)</option>
<option value="-90">-90&deg; (Left)</option>
</select>
</div>
<div class="input-group" id="dcw-group">
<label for="dcw">DCW (Downsize EN)</label>
<div class="switch">
<input id="dcw" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="dcw"></label>
</div>
</div>
<div class="input-group" id="colorbar-group">
<label for="colorbar">Test Pattern</label>
<div class="switch">
<input id="colorbar" type="checkbox" class="default-action">
<label class="slider" for="colorbar"></label>
</div>
</div>
<div class="input-group" id="min_frame_time-group" title="Minimum frame time&#013;Higher settings reduce the frame rate&#013;Use this for a smoother stream and to reduce load on the WiFi and browser">
<label for="min_frame_time">Frame Duration Limit</label>
<select id="min_frame_time" class="default-action">
<option value="3333">3.3s (0.3fps)</option>
<option value="2000">2s (0.5fps)</option>
<option value="1000">1s (1fps)</option>
<option value="500">500ms (2fps)</option>
<option value="333">333ms (3fps)</option>
<option value="200">200ms (5fps)</option>
<option value="100">100ms (10fps)</option>
<option value="50">50ms (20fps)</option>
<option value="0" selected="selected">Disabled</option>
</select>
</div>
<div class="input-group" id="preferences-group">
<label for="prefs" style="line-height: 2em;">Preferences</label>
<button id="reboot" title="Reboot the camera module">Reboot</button>
<button id="save_prefs" title="Save Preferences on camera module">Save</button>
<button id="clear_prefs" title="Erase saved Preferences on camera module">Erase</button>
</div>
<div class="input-group" id="cam_name-group">
<label for="cam_name">
<a href="/dump" title="System Info" target="_blank">Name</a></label>
<div id="cam_name" class="default-action"></div>
</div>
<div class="input-group" id="code_ver-group">
<label for="code_ver">
<a href="https://github.com/easytarget/esp32-cam-webserver"
title="ESP32 Cam Webserver on GitHub" target="_blank">Firmware</a></label>
<div id="code_ver" class="default-action"></div>
</div>
<div class="input-group hidden" id="stream-group">
<label for="stream_url" id="stream_link">Stream</label>
<div id="stream_url" class="default-action">Unknown</div>
</div>
</nav>
</div>
<figure>
<div id="stream-container" class="image-container hidden">
<div class="close close-rot-none" id="close-stream">×</div>
<img id="stream" src="">
</div>
</figure>
</div>
</section>
</body>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
var baseHost = document.location.origin;
var streamURL = 'Undefined';
var viewerURL = 'Undefined';
const header = document.getElementById('logo')
const settings = document.getElementById('sidebar')
const waitSettings = document.getElementById('wait-settings')
const lampGroup = document.getElementById('lamp-group')
const autolampGroup = document.getElementById('autolamp-group')
const streamGroup = document.getElementById('stream-group')
const camName = document.getElementById('cam_name')
const codeVer = document.getElementById('code_ver')
const rotate = document.getElementById('rotate')
const view = document.getElementById('stream')
const viewContainer = document.getElementById('stream-container')
const stillButton = document.getElementById('get-still')
const streamButton = document.getElementById('toggle-stream')
const closeButton = document.getElementById('close-stream')
const streamLink = document.getElementById('stream_link')
const framesize = document.getElementById('framesize')
const xclk = document.getElementById('xclk')
const swapButton = document.getElementById('swap-viewer')
const savePrefsButton = document.getElementById('save_prefs')
const clearPrefsButton = document.getElementById('clear_prefs')
const rebootButton = document.getElementById('reboot')
const minFrameTime = document.getElementById('min_frame_time')
const hide = el => {
el.classList.add('hidden')
}
const show = el => {
el.classList.remove('hidden')
}
const disable = el => {
el.classList.add('disabled')
el.disabled = true
}
const enable = el => {
el.classList.remove('disabled')
el.disabled = false
}
const updateValue = (el, value, updateRemote) => {
updateRemote = updateRemote == null ? true : updateRemote
let initialValue
if (el.type === 'checkbox') {
initialValue = el.checked
value = !!value
el.checked = value
} else {
initialValue = el.value
el.value = value
}
if (updateRemote && initialValue !== value) {
updateConfig(el);
} else if(!updateRemote){
if(el.id === "aec"){
value ? hide(exposure) : show(exposure)
} else if(el.id === "agc"){
if (value) {
show(gainCeiling)
hide(agcGain)
} else {
hide(gainCeiling)
show(agcGain)
}
} else if(el.id === "awb_gain"){
value ? show(wb) : hide(wb)
} else if(el.id === "lamp"){
if (value == -1) {
hide(lampGroup)
hide(autolampGroup)
} else {
show(lampGroup)
show(autolampGroup)
}
} else if(el.id === "cam_name"){
camName.innerHTML = value;
window.document.title = value;
console.log('Name set to: ' + value);
} else if(el.id === "code_ver"){
codeVer.innerHTML = value;
console.log('Firmware Build: ' + value);
} else if(el.id === "rotate"){
rotate.value = value;
applyRotation();
} else if(el.id === "min_frame_time"){
min_frame_time.value = value;
} else if(el.id === "stream_url"){
streamURL = value;
viewerURL = value + 'view';
stream_url.innerHTML = value;
stream_link.setAttribute("title", `Open the standalone stream viewer :: ${viewerURL}`);
stream_link.style.textDecoration = "underline";
stream_link.style.cursor = "pointer";
streamButton.setAttribute("title", `Start the stream :: ${streamURL}`);
show(streamGroup)
console.log('Stream URL set to: ' + streamURL);
console.log('Stream Viewer URL set to: ' + viewerURL);
}
}
}
var rangeUpdateScheduled = false
var latestRangeConfig
function updateRangeConfig (el) {
latestRangeConfig = el
if (!rangeUpdateScheduled) {
rangeUpdateScheduled = true;
setTimeout(function(){
rangeUpdateScheduled = false
updateConfig(latestRangeConfig)
}, 150);
}
}
function updateConfig (el) {
let value
switch (el.type) {
case 'checkbox':
value = el.checked ? 1 : 0
break
case 'range':
case 'number':
case 'select-one':
value = el.value
break
case 'button':
case 'submit':
value = '1'
break
default:
return
}
const query = `${baseHost}/control?var=${el.id}&val=${value}`
fetch(query)
.then(response => {
console.log(`request to ${query} finished, status: ${response.status}`)
})
}
document
.querySelectorAll('.close')
.forEach(el => {
el.onclick = () => {
hide(el.parentNode)
}
})
// read initial values
fetch(`${baseHost}/status`)
.then(function (response) {
return response.json()
})
.then(function (state) {
document
.querySelectorAll('.default-action')
.forEach(el => {
updateValue(el, state[el.id], false)
})
hide(waitSettings);
show(settings);
show(streamButton);
//startStream();
})
// Put some helpful text on the 'Still' button
stillButton.setAttribute("title", `Capture a still image :: ${baseHost}/capture`);
const stopStream = () => {
window.stop();
streamButton.innerHTML = 'Start Stream';
streamButton.setAttribute("title", `Start the stream :: ${streamURL}`);
hide(viewContainer);
}
const startStream = () => {
view.src = streamURL;
view.scrollIntoView(false);
streamButton.innerHTML = 'Stop Stream';
streamButton.setAttribute("title", `Stop the stream`);
show(viewContainer);
}
const applyRotation = () => {
rot = rotate.value;
if (rot == -90) {
viewContainer.style.transform = `rotate(-90deg) translate(-100%)`;
closeButton.classList.remove('close-rot-none');
closeButton.classList.remove('close-rot-right');
closeButton.classList.add('close-rot-left');
} else if (rot == 90) {
viewContainer.style.transform = `rotate(90deg) translate(0, -100%)`;
closeButton.classList.remove('close-rot-left');
closeButton.classList.remove('close-rot-none');
closeButton.classList.add('close-rot-right');
} else {
viewContainer.style.transform = `rotate(0deg)`;
closeButton.classList.remove('close-rot-left');
closeButton.classList.remove('close-rot-right');
closeButton.classList.add('close-rot-none');
}
console.log('Rotation ' + rot + ' applied');
}
// Attach actions to controls
streamLink.onclick = () => {
stopStream();
window.open(viewerURL, "_blank");
}
stillButton.onclick = () => {
stopStream();
view.src = `${baseHost}/capture?_cb=${Date.now()}`;
view.scrollIntoView(false);
show(viewContainer);
}
closeButton.onclick = () => {
stopStream();
hide(viewContainer);
}
streamButton.onclick = () => {
const streamEnabled = streamButton.innerHTML === 'Stop Stream'
if (streamEnabled) {
stopStream();
} else {
startStream();
}
}
// Attach default on change action
document
.querySelectorAll('.default-action')
.forEach(el => {
el.onchange = () => updateConfig(el)
})
// Update range sliders as they are being moved
document
.querySelectorAll('input[type="range"]')
.forEach(el => {
el.oninput = () => updateRangeConfig(el)
})
// Custom actions
// Gain
const agc = document.getElementById('agc')
const agcGain = document.getElementById('agc_gain-group')
const gainCeiling = document.getElementById('gainceiling-group')
agc.onchange = () => {
updateConfig(agc)
if (agc.checked) {
show(gainCeiling)
hide(agcGain)
} else {
hide(gainCeiling)
show(agcGain)
}
}
// Exposure
const aec = document.getElementById('aec')
const exposure = document.getElementById('aec_value-group')
aec.onchange = () => {
updateConfig(aec)
aec.checked ? hide(exposure) : show(exposure)
}
// AWB
const awb = document.getElementById('awb_gain')
const wb = document.getElementById('wb_mode-group')
awb.onchange = () => {
updateConfig(awb)
awb.checked ? show(wb) : hide(wb)
}
// Detection and framesize
rotate.onchange = () => {
applyRotation();
updateConfig(rotate);
}
framesize.onchange = () => {
updateConfig(framesize)
}
minFrameTime.onchange = () => {
updateConfig(minFrameTime)
}
xclk.onchange = () => {
console.log("xclk:" , xclk);
updateConfig(xclk)
}
swapButton.onclick = () => {
window.open('/?view=simple','_self');
}
savePrefsButton.onclick = () => {
if (confirm("Save the current preferences?")) {
updateConfig(savePrefsButton);
}
}
clearPrefsButton.onclick = () => {
if (confirm("Remove the saved preferences?")) {
updateConfig(clearPrefsButton);
}
}
rebootButton.onclick = () => {
if (confirm("Reboot the Camera Module?")) {
updateConfig(rebootButton);
// Some sort of countdown here?
hide(settings);
hide(viewContainer);
header.innerHTML = '<h1>Rebooting!</h1><hr>Page will reload after 30 seconds.';
setTimeout(function() {
location.replace(document.URL);
}, 30000);
}
}
})
</script>
</html>)=====";
size_t index_ov2640_html_len = sizeof(index_ov2640_html)-1;

View File

@@ -0,0 +1,642 @@
/*
* primary HTML for the OV3660 camera module
*/
const uint8_t index_ov3660_html[] = R"=====(<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 OV3660</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="stylesheet" type="text/css" href="/style.css">
<style>
@media (min-width: 800px) and (orientation:landscape) {
#content {
display:flex;
flex-wrap: nowrap;
align-items: stretch
}
}
</style>
</head>
<body>
<section class="main">
<div id="logo">
<label for="nav-toggle-cb" id="nav-toggle" style="float:left;">&#9776;&nbsp;&nbsp;Settings&nbsp;&nbsp;&nbsp;&nbsp;</label>
<button id="swap-viewer" style="float:left;" title="Swap to simple viewer">Simple</button>
<button id="get-still" style="float:left;">Get Still</button>
<button id="toggle-stream" style="float:left;" class="hidden">Start Stream</button>
<div id="wait-settings" style="float:left;" class="loader" title="Waiting for camera settings to load"></div>
</div>
<div id="content">
<div class="hidden" id="sidebar">
<input type="checkbox" id="nav-toggle-cb" checked="checked">
<nav id="menu">
<div class="input-group hidden" id="lamp-group" title="Flashlight LED.&#013;&#013;Warning:&#013;Built-In lamps can be Very Bright! Avoid looking directly at LED&#013;Can draw a lot of power and may cause visual artifacts, affect WiFi or even brownout the camera on high settings">
<label for="lamp">Light</label>
<div class="range-min">Off</div>
<input type="range" id="lamp" min="0" max="100" value="0" class="default-action">
<div class="range-max"><span style="font-size: 125%;">&#9888;</span>Full</div>
</div>
<div class="input-group hidden" id="autolamp-group" title="When enabled the lamp will only turn on while the camera is active">
<label for="autolamp">Auto Lamp</label>
<div class="switch">
<input id="autolamp" type="checkbox" class="default-action">
<label class="slider" for="autolamp"></label>
</div>
</div>
<div class="input-group" id="framesize-group" title="Camera resolution&#013;Higher resolutions will result in lower framerates">
<label for="framesize">Resolution</label>
<select id="framesize" class="default-action">
<option value="17">QXGA (2048x1536)</option>
<option value="14">FHD (1920x1080)</option>
<option value="13">UXGA (1600x1200)</option>
<option value="12">SXGA (1280x1024)</option>
<option value="11">HD (1280x720)</option>
<option value="10">XGA (1024x768)</option>
<option value="9">SVGA (800x600)</option>
<option value="8">VGA (640x480)</option>
<option value="7">HVGA (480x320)</option>
<option value="6">CIF (400x296)</option>
<option value="5">QVGA (320x240)</option>
<option value="3">HQVGA (240x176)</option>
<option value="1">QQVGA (160x120)</option>
<option value="0">THUMB (96x96)</option>
</select>
</div>
<div class="input-group" id="quality-group" title="Camera Image and Stream quality factor&#013;Higher settings will result in lower framerates">
<label for="quality">Quality</label>
<div class="range-min">Low</div>
<!-- Note; the following element is 'flipped' in CSS so that it slides from High to Low
As a result the 'min' and 'max' values are reversed here too -->
<input type="range" id="quality" min="4" max="63" value="10" class="default-action">
<div class="range-max">High</div>
</div>
<div class="input-group" id="set-xclk-group" title="Camera Bus Clock Frequency&#013;Increasing this will raise the camera framerate and capture speed&#013;&#013;Raising too far will result in visual artifacts and/or incomplete frames&#013;This setting can vary a lot between boards, budget boards typically need lower values">
<label for="set-xclk">XCLK</label>
<div class="text">
<input id="xclk" type="number" min="2" max="32" size="3" step="1" class="default-action">
<div class="range-max">MHz</div>
</div>
</div>
<div class="input-group" id="brightness-group">
<label for="brightness">Brightness</label>
<div class="range-min">-3</div>
<input type="range" id="brightness" min="-3" max="3" value="0" class="default-action">
<div class="range-max">3</div>
</div>
<div class="input-group" id="contrast-group">
<label for="contrast">Contrast</label>
<div class="range-min">-3</div>
<input type="range" id="contrast" min="-3" max="3" value="0" class="default-action">
<div class="range-max">3</div>
</div>
<div class="input-group" id="saturation-group">
<label for="saturation">Saturation</label>
<div class="range-min">-4</div>
<input type="range" id="saturation" min="-4" max="4" value="0" class="default-action">
<div class="range-max">4</div>
</div>
<div class="input-group" id="sharpness-group">
<label for="sharpness">Sharpness</label>
<div class="range-min">-3</div>
<input type="range" id="sharpness" min="-3" max="3" value="0" class="default-action">
<div class="range-max">3</div>
</div>
<div class="input-group" id="denoise-group">
<label for="denoise">De-Noise</label>
<div class="range-min">Auto</div>
<input type="range" id="denoise" min="0" max="8" value="0" class="default-action">
<div class="range-max">8</div>
</div>
<div class="input-group" id="ae_level-group">
<label for="ae_level">Exposure Level</label>
<div class="range-min">-5</div>
<input type="range" id="ae_level" min="-5" max="5" value="0" class="default-action">
<div class="range-max">5</div>
</div>
<div class="input-group" id="gainceiling-group">
<label for="gainceiling">Gainceiling</label>
<div class="range-min">0</div>
<input type="range" id="gainceiling" min="0" max="511" value="0" class="default-action">
<div class="range-max">511</div>
</div>
<div class="input-group" id="special_effect-group">
<label for="special_effect">Special Effect</label>
<select id="special_effect" class="default-action">
<option value="0" selected="selected">No Effect</option>
<option value="1">Negative</option>
<option value="2">Grayscale</option>
<option value="3">Red Tint</option>
<option value="4">Green Tint</option>
<option value="5">Blue Tint</option>
<option value="6">Sepia</option>
</select>
</div>
<div class="input-group" id="awb-group">
<label for="awb">AWB Enable</label>
<div class="switch">
<input id="awb" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="awb"></label>
</div>
</div>
<div class="input-group" id="dcw-group">
<label for="dcw">Advanced AWB</label>
<div class="switch">
<input id="dcw" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="dcw"></label>
</div>
</div>
<div class="input-group" id="awb_gain-group">
<label for="awb_gain">Manual AWB Gain</label>
<div class="switch">
<input id="awb_gain" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="awb_gain"></label>
</div>
</div>
<div class="input-group" id="wb_mode-group">
<label for="wb_mode">WB Mode</label>
<select id="wb_mode" class="default-action">
<option value="0" selected="selected">Auto</option>
<option value="1">Sunny</option>
<option value="2">Cloudy</option>
<option value="3">Office</option>
<option value="4">Home</option>
</select>
</div>
<div class="input-group" id="aec-group">
<label for="aec">AEC Sensor Enable</label>
<div class="switch">
<input id="aec" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="aec"></label>
</div>
</div>
<div class="input-group" id="aec_value-group">
<label for="aec_value">Manual Exposure</label>
<div class="range-min">0</div>
<input type="range" id="aec_value" min="0" max="1536" value="320" class="default-action">
<div class="range-max">1536</div>
</div>
<div class="input-group" id="aec2-group">
<label for="aec2">Night Mode</label>
<div class="switch">
<input id="aec2" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="aec2"></label>
</div>
</div>
<div class="input-group" id="agc-group">
<label for="agc">AGC</label>
<div class="switch">
<input id="agc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="agc"></label>
</div>
</div>
<div class="input-group hidden" id="agc_gain-group">
<label for="agc_gain">Gain</label>
<div class="range-min">1x</div>
<input type="range" id="agc_gain" min="0" max="64" value="5" class="default-action">
<div class="range-max">64x</div>
</div>
<div class="input-group" id="raw_gma-group">
<label for="raw_gma">Raw GMA Enable</label>
<div class="switch">
<input id="raw_gma" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="raw_gma"></label>
</div>
</div>
<div class="input-group" id="lenc-group">
<label for="lenc">Lens Correction</label>
<div class="switch">
<input id="lenc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="lenc"></label>
</div>
</div>
<div class="input-group" id="hmirror-group">
<label for="hmirror">H-Mirror Stream</label>
<div class="switch">
<input id="hmirror" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="hmirror"></label>
</div>
</div>
<div class="input-group" id="vflip-group">
<label for="vflip">V-Flip Stream</label>
<div class="switch">
<input id="vflip" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="vflip"></label>
</div>
</div>
<div class="input-group" id="rotate-group">
<label for="rotate">Rotate in Browser</label>
<select id="rotate" class="default-action">
<option value="90">90&deg; (Right)</option>
<option value="0" selected="selected">0&deg; (None)</option>
<option value="-90">-90&deg; (Left)</option>
</select>
</div>
<div class="input-group" id="bpc-group">
<label for="bpc">BPC</label>
<div class="switch">
<input id="bpc" type="checkbox" class="default-action">
<label class="slider" for="bpc"></label>
</div>
</div>
<div class="input-group" id="wpc-group">
<label for="wpc">WPC</label>
<div class="switch">
<input id="wpc" type="checkbox" class="default-action" checked="checked">
<label class="slider" for="wpc"></label>
</div>
</div>
<div class="input-group" id="colorbar-group">
<label for="colorbar">Test Pattern</label>
<div class="switch">
<input id="colorbar" type="checkbox" class="default-action">
<label class="slider" for="colorbar"></label>
</div>
</div>
<div class="input-group" id="min_frame_time-group" title="Minimum frame time&#013;Higher settings reduce the frame rate&#013;Use this for a smoother stream and to reduce load on the WiFi and browser">
<label for="min_frame_time">Frame Duration Limit</label>
<select id="min_frame_time" class="default-action">
<option value="3333">3.3s (0.3fps)</option>
<option value="2000">2s (0.5fps)</option>
<option value="1000">1s (1fps)</option>
<option value="500">500ms (2fps)</option>
<option value="333">333ms (3fps)</option>
<option value="200">200ms (5fps)</option>
<option value="100">100ms (10fps)</option>
<option value="50">50ms (20fps)</option>
<option value="0" selected="selected">Disabled</option>
</select>
</div>
<div class="input-group" id="preferences-group">
<label for="prefs" style="line-height: 2em;">Preferences</label>
<button id="reboot" title="Reboot the camera module">Reboot</button>
<button id="save_prefs" title="Save Preferences on camera module">Save</button>
<button id="clear_prefs" title="Erase saved Preferences on camera module">Erase</button>
</div>
<div class="input-group" id="cam_name-group">
<label for="cam_name">
<a href="/dump" title="System Info" target="_blank">Name</a></label>
<div id="cam_name" class="default-action"></div>
</div>
<div class="input-group" id="code_ver-group">
<label for="code_ver">
<a href="https://github.com/easytarget/esp32-cam-webserver"
title="ESP32 Cam Webserver on GitHub" target="_blank">Firmware</a></label>
<div id="code_ver" class="default-action"></div>
</div>
<div class="input-group hidden" id="stream-group">
<label for="stream_url" id="stream_link">Stream</label>
<div id="stream_url" class="default-action">Unknown</div>
</div>
</nav>
</div>
<figure>
<div id="stream-container" class="image-container hidden">
<div class="close close-rot-none" id="close-stream">×</div>
<img id="stream" src="">
</div>
</figure>
</div>
</section>
</body>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
var baseHost = document.location.origin;
var streamURL = 'Undefined';
var viewerURL = 'Undefined';
const header = document.getElementById('logo')
const settings = document.getElementById('sidebar')
const waitSettings = document.getElementById('wait-settings')
const lampGroup = document.getElementById('lamp-group')
const autolampGroup = document.getElementById('autolamp-group')
const streamGroup = document.getElementById('stream-group')
const camName = document.getElementById('cam_name')
const codeVer = document.getElementById('code_ver')
const rotate = document.getElementById('rotate')
const view = document.getElementById('stream')
const viewContainer = document.getElementById('stream-container')
const stillButton = document.getElementById('get-still')
const streamButton = document.getElementById('toggle-stream')
const closeButton = document.getElementById('close-stream')
const streamLink = document.getElementById('stream_link')
const framesize = document.getElementById('framesize')
const xclk = document.getElementById('xclk')
const swapButton = document.getElementById('swap-viewer')
const savePrefsButton = document.getElementById('save_prefs')
const clearPrefsButton = document.getElementById('clear_prefs')
const rebootButton = document.getElementById('reboot')
const minFrameTime = document.getElementById('min_frame_time')
const hide = el => {
el.classList.add('hidden')
}
const show = el => {
el.classList.remove('hidden')
}
const disable = el => {
el.classList.add('disabled')
el.disabled = true
}
const enable = el => {
el.classList.remove('disabled')
el.disabled = false
}
const updateValue = (el, value, updateRemote) => {
updateRemote = updateRemote == null ? true : updateRemote
let initialValue
if (el.type === 'checkbox') {
initialValue = el.checked
value = !!value
el.checked = value
} else {
initialValue = el.value
el.value = value
}
if (updateRemote && initialValue !== value) {
updateConfig(el);
} else if(!updateRemote){
if(el.id === "aec"){
value ? hide(exposure) : show(exposure)
} else if(el.id === "agc"){
if (value) {
hide(agcGain)
} else {
show(agcGain)
}
} else if(el.id === "awb_gain"){
value ? show(wb) : hide(wb)
} else if(el.id === "lamp"){
if (value == -1) {
hide(lampGroup)
hide(autolampGroup)
} else {
show(lampGroup)
show(autolampGroup)
}
} else if(el.id === "cam_name"){
camName.innerHTML = value;
window.document.title = value;
console.log('Name set to: ' + value);
} else if(el.id === "code_ver"){
codeVer.innerHTML = value;
console.log('Firmware Build: ' + value);
} else if(el.id === "rotate"){
rotate.value = value;
applyRotation();
} else if(el.id === "min_frame_time"){
min_frame_time.value = value;
} else if(el.id === "stream_url"){
streamURL = value;
viewerURL = value + 'view';
stream_url.innerHTML = value;
stream_link.setAttribute("title", `Open the standalone stream viewer :: ${viewerURL}`);
stream_link.style.textDecoration = "underline";
stream_link.style.cursor = "pointer";
streamButton.setAttribute("title", `Start the stream :: ${streamURL}`);
show(streamGroup)
console.log('Stream URL set to: ' + streamURL);
console.log('Stream Viewer URL set to: ' + viewerURL);
}
}
}
var rangeUpdateScheduled = false
var latestRangeConfig
function updateRangeConfig (el) {
latestRangeConfig = el
if (!rangeUpdateScheduled) {
rangeUpdateScheduled = true;
setTimeout(function(){
rangeUpdateScheduled = false
updateConfig(latestRangeConfig)
}, 150);
}
}
function updateConfig (el) {
let value
switch (el.type) {
case 'checkbox':
value = el.checked ? 1 : 0
break
case 'range':
case 'number':
case 'select-one':
value = el.value
break
case 'button':
case 'submit':
value = '1'
break
default:
return
}
const query = `${baseHost}/control?var=${el.id}&val=${value}`
fetch(query)
.then(response => {
console.log(`request to ${query} finished, status: ${response.status}`)
})
}
document
.querySelectorAll('.close')
.forEach(el => {
el.onclick = () => {
hide(el.parentNode)
}
})
// read initial values
fetch(`${baseHost}/status`)
.then(function (response) {
return response.json()
})
.then(function (state) {
document
.querySelectorAll('.default-action')
.forEach(el => {
updateValue(el, state[el.id], false)
})
hide(waitSettings);
show(settings);
show(streamButton);
//startStream();
})
// Put some helpful text on the 'Still' button
stillButton.setAttribute("title", `Capture a still image :: ${baseHost}/capture`);
const stopStream = () => {
window.stop();
streamButton.innerHTML = 'Start Stream';
streamButton.setAttribute("title", `Start the stream :: ${streamURL}`);
hide(viewContainer);
}
const startStream = () => {
view.src = streamURL;
view.scrollIntoView(false);
streamButton.innerHTML = 'Stop Stream';
streamButton.setAttribute("title", `Stop the stream`);
show(viewContainer);
}
const applyRotation = () => {
rot = rotate.value;
if (rot == -90) {
viewContainer.style.transform = `rotate(-90deg) translate(-100%)`;
closeButton.classList.remove('close-rot-none');
closeButton.classList.remove('close-rot-right');
closeButton.classList.add('close-rot-left');
} else if (rot == 90) {
viewContainer.style.transform = `rotate(90deg) translate(0, -100%)`;
closeButton.classList.remove('close-rot-left');
closeButton.classList.remove('close-rot-none');
closeButton.classList.add('close-rot-right');
} else {
viewContainer.style.transform = `rotate(0deg)`;
closeButton.classList.remove('close-rot-left');
closeButton.classList.remove('close-rot-right');
closeButton.classList.add('close-rot-none');
}
console.log('Rotation ' + rot + ' applied');
}
// Attach actions to controls
streamLink.onclick = () => {
stopStream();
window.open(viewerURL, "_blank");
}
stillButton.onclick = () => {
stopStream();
view.src = `${baseHost}/capture?_cb=${Date.now()}`;
view.scrollIntoView(false);
show(viewContainer);
}
closeButton.onclick = () => {
stopStream();
hide(viewContainer);
}
streamButton.onclick = () => {
const streamEnabled = streamButton.innerHTML === 'Stop Stream'
if (streamEnabled) {
stopStream();
} else {
startStream();
}
}
// Attach default on change action
document
.querySelectorAll('.default-action')
.forEach(el => {
el.onchange = () => updateConfig(el)
})
// Update range sliders as they are being moved
document
.querySelectorAll('input[type="range"]')
.forEach(el => {
el.oninput = () => updateRangeConfig(el)
})
// Custom actions
// Gain
const agc = document.getElementById('agc')
const agcGain = document.getElementById('agc_gain-group')
agc.onchange = () => {
updateConfig(agc)
if (agc.checked) {
hide(agcGain)
} else {
show(agcGain)
}
}
// Exposure
const aec = document.getElementById('aec')
const exposure = document.getElementById('aec_value-group')
aec.onchange = () => {
updateConfig(aec)
aec.checked ? hide(exposure) : show(exposure)
}
// AWB
const awb = document.getElementById('awb_gain')
const wb = document.getElementById('wb_mode-group')
awb.onchange = () => {
updateConfig(awb)
awb.checked ? show(wb) : hide(wb)
}
// Detection and framesize
rotate.onchange = () => {
applyRotation();
updateConfig(rotate);
}
framesize.onchange = () => {
updateConfig(framesize)
}
minFrameTime.onchange = () => {
updateConfig(minFrameTime)
}
xclk.onchange = () => {
console.log("xclk:" , xclk);
updateConfig(xclk)
}
swapButton.onclick = () => {
window.open('/?view=simple','_self');
}
savePrefsButton.onclick = () => {
if (confirm("Save the current preferences?")) {
updateConfig(savePrefsButton);
}
}
clearPrefsButton.onclick = () => {
if (confirm("Remove the saved preferences?")) {
updateConfig(clearPrefsButton);
}
}
rebootButton.onclick = () => {
if (confirm("Reboot the Camera Module?")) {
updateConfig(rebootButton);
// Some sort of countdown here?
hide(settings);
hide(viewContainer);
header.innerHTML = '<h1>Rebooting!</h1><hr>Page will reload after 30 seconds.';
setTimeout(function() {
location.replace(document.URL);
}, 30000);
}
}
})
</script>
</html>)=====";
size_t index_ov3660_html_len = sizeof(index_ov3660_html)-1;

197
ESP32_CAM_NEW/myconfig.h Normal file
View File

@@ -0,0 +1,197 @@
/*
* Rename this example to 'myconfig.h' and fill in your details.
*
* The local config is in the '.gitignore' file, which helps to keep details secret.
*/
/* Give the camera a name for the web interface */
#define CAM_NAME "ESP32 camera server"
/*
* Give the network name
* It will be used as the hostname in ST modes
* This is the name the camera will advertise on the network (mdns) for services and OTA
*/
#define MDNS_NAME "esp32-cam"
/*
* WiFi Settings
*
* For the simplest connection to an existing network
* just replace your ssid and password in the line below.
*/
struct station stationList[] = {{"Livebox-37cc","8A6060920A8A86896F770F2C47", true}};
/*
* You can extend the stationList[] above with additional SSID+Password pairs
struct station stationList[] = {{"ssid1", "pass1", true},
{"ssid2", "pass2", true},
{"ssid3", "pass3", false}};
* Note the use of nested braces '{' and '}' to group each entry, and commas ',' to separate them.
*
* The first entry (ssid1, above) in the stationList is special, if WIFI_AP_ENABLE has been uncommented (below)
* it will be used for the AccessPoint ssid and password. See the comments there for more.
*
* The 'dhcp' setting controls whether the station uses DHCP or static IP settings; if in doubt leave 'true'
*
* You can also use a BSSID (eg: "2F:67:94:F5:BB:6A", a colon separated mac address string) in place of
* the ssid to force connections to specific networks even when the ssid's collide,
*/
/* Extended WiFi Settings */
/*
* If defined: URL_HOSTNAME will be used in place of the IP address in internal URL's
*/
// #define URL_HOSTNAME "esp32-cam"
/*
* Static network settings for client mode
*
* Note: The same settings will be applied to all client connections where the dhcp setting is 'false'
* You must define all three: IP, Gateway and NetMask
*/
// warning - IP addresses must be separated with commas (,) and not decimals (.)
// #define ST_IP 192,168,0,123
// #define ST_GATEWAY 192,168,0,2
// #define ST_NETMASK 255,255,255,0
// One or two DNS servers can be supplied, only the NTP code currently uses them
// #define ST_DNS1 192,168,0,2
// #define ST_DNS2 8,8,8,8
/*
* AccessPoint;
*
* Uncomment to enable AP mode;
*
*/
// #define WIFI_AP_ENABLE
/* AP Mode Notes:
*
* Once enabled the AP ssid and password will be taken from the 1st entry in the stationList[] above.
*
* If there are further entries listed they will be scanned at startup in the normal way and connected to
* if they are found. AP then works as a fallback mode for when there are no 'real' networks available.
*
* Setting the 'dhcp' field to true for the AP enables a captive portal and attempts to send
* all visitors to the webcam page, with varying degrees of success depending on the visitors
* browser and other settings.
*/
// Optionally change the AccessPoint ip address (default = 192.168.4.1)
// warning - IP addresses must be separated with commas (,) and not decimals (.)
// #define AP_ADDRESS 192,168,4,1
// Uncomment this to force the AccessPoint channel number, default = 1
// #define AP_CHAN 1
/*
* Port numbers for WebUI and Stream, defaults to 80 and 81.
* Uncomment and edit as appropriate
*/
// #define HTTP_PORT 80
// #define STREAM_PORT 81
/*
* Wifi Watchdog defines how long we spend waiting for a connection before retrying,
* and how often we check to see if we are still connected, milliseconds
* You may wish to increase this if your WiFi is slow at conencting.
*/
// #define WIFI_WATCHDOG 15000
/*
* Over The Air firmware updates can be disabled by uncommenting the folowing line
* When enabled the device will advertise itself using the MDNS_NAME defined above
*/
// #define NO_OTA
/*
* OTA can be password protected to prevent the device being hijacked
*/
// #define OTA_PASSWORD "SuperVisor"
/* NTP
* Uncomment the following to enable the on-board clock
* Pick a nearby pool server from: https://www.ntppool.org/zone/@
* Set the GMT offset to match your timezone IN SECONDS;
* see https://en.wikipedia.org/wiki/List_of_UTC_time_offsets
* 1hr = 3600 seconds; do the math ;-)
* Default is CET (Central European Time), eg GMT + 1hr
* The DST offset is usually 1 hour (again, in seconds) if used in your country.
*/
//#define NTPSERVER "<EDIT THIS>.pool.ntp.org"
//#define NTP_GMT_OFFSET 3600
//#define NTP_DST_OFFSET 3600
/*
* Camera Defaults
*
*/
// Initial Reslolution, default SVGA
// available values are: FRAMESIZE_[THUMB|QQVGA|HQVGA|QVGA|CIF|HVGA|VGA|SVGA|XGA|HD|SXGA|UXGA] + [FHD|QXGA] for 3Mp Sensors; eg ov3660
#define DEFAULT_RESOLUTION FRAMESIZE_HD
// Hardware Horizontal Mirror, 0 or 1 (overrides default board setting)
// #define H_MIRROR 0
// Hardware Vertical Flip , 0 or 1 (overrides default board setting)
// #define V_FLIP 1
// Browser Rotation (one of: -90,0,90, default 0)
// #define CAM_ROTATION 0
// Minimal frame duration in ms, used to limit max FPS
// max_fps = 1000/min_frame_time
// #define MIN_FRAME_TIME 500
/*
* Additional Features
*
*/
// Default Page: uncomment to make the full control page the default, otherwise show simple viewer
// #define DEFAULT_INDEX_FULL
// Uncomment to disable the notification LED on the module
// #define LED_DISABLE
// Uncomment to disable the illumination lamp features
// #define LAMP_DISABLE
// Define the startup lamp power setting (as a percentage, defaults to 0%)
// Saved (SPIFFS) user settings will override this
// #define LAMP_DEFAULT 0
// Assume the module used has a SPIFFS/LittleFS partition, and use that for persistent setting storage
// Uncomment to disable this this, the controls will still be shown in the UI but are inoperative.
#define NO_FS
// Uncomment to enable camera debug info on serial by default
// #define DEBUG_DEFAULT_ON
/*
* Camera Hardware Selectiom
*
* You must uncomment one, and only one, of the lines below to select your board model.
* Remember to also select the board in the Boards Manager
* This is not optional
*/
#define CAMERA_MODEL_AI_THINKER // default
// #define CAMERA_MODEL_WROVER_KIT
// #define CAMERA_MODEL_ESP_EYE
// #define CAMERA_MODEL_M5STACK_PSRAM
// #define CAMERA_MODEL_M5STACK_V2_PSRAM
// #define CAMERA_MODEL_M5STACK_WIDE
// #define CAMERA_MODEL_M5STACK_ESP32CAM // Originally: CAMERA_MODEL_M5STACK_NO_PSRAM
// #define CAMERA_MODEL_TTGO_T_JOURNAL
// #define CAMERA_MODEL_ARDUCAM_ESP32S_UNO
// Initial Camera module bus communications frequency
// Currently defaults to 8MHz
// The post-initialisation (runtime) value can be set and edited by the user in the UI
// For clone modules that have camera module and SPIFFS startup issues try setting
// this very low (start at 2MHZ and increase):
// #define XCLK_FREQ_MHZ 2

211
ESP32_CAM_NEW/storage.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include "esp_camera.h"
#include "src/jsonlib/jsonlib.h"
#include "storage.h"
// These are defined in the main .ino file
extern void flashLED(int flashtime);
extern int myRotation; // Rotation
extern int lampVal; // The current Lamp value
extern bool autoLamp; // Automatic lamp mode
extern int xclk; // Camera module clock speed
extern int minFrameTime; // Limits framerate
/*
* Useful utility when debugging...
*/
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing SPIFFS directory: %s\r\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("- failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println(" - not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print("\tSIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void dumpPrefs(fs::FS &fs){
if (fs.exists(PREFERENCES_FILE)) {
// Dump contents for debug
File file = fs.open(PREFERENCES_FILE, FILE_READ);
int countSize = 0;
while (file.available() && countSize <= PREFERENCES_MAX_SIZE) {
Serial.print(char(file.read()));
countSize++;
}
Serial.println("");
file.close();
} else {
Serial.printf("%s not found, nothing to dump.\r\n", PREFERENCES_FILE);
}
}
void loadPrefs(fs::FS &fs){
if (fs.exists(PREFERENCES_FILE)) {
// read file into a string
String prefs;
Serial.printf("Loading preferences from file %s\r\n", PREFERENCES_FILE);
File file = fs.open(PREFERENCES_FILE, FILE_READ);
if (!file) {
Serial.println("Failed to open preferences file for reading, maybe corrupt, removing");
removePrefs(SPIFFS);
return;
}
size_t size = file.size();
if (size > PREFERENCES_MAX_SIZE) {
Serial.println("Preferences file size is too large, maybe corrupt, removing");
removePrefs(SPIFFS);
return;
}
while (file.available()) {
prefs += char(file.read());
if (prefs.length() > size) {
// corrupted SPIFFS files can return data beyond their declared size.
Serial.println("Preferences file failed to load properly, appears to be corrupt, removing");
removePrefs(SPIFFS);
return;
}
}
// get sensor reference
sensor_t * s = esp_camera_sensor_get();
// process local settings
if (lampVal >= 0) {
int lampValPref = jsonExtract(prefs, "lamp").toInt();
if (lampValPref >= 0) lampVal = lampValPref;
}
minFrameTime = jsonExtract(prefs, "min_frame_time").toInt();
if (jsonExtract(prefs, "autolamp").toInt() == 0) autoLamp = false; else autoLamp = true;
int xclkPref = jsonExtract(prefs, "xclk").toInt();
if (xclkPref >= 2) xclk = xclkPref;
myRotation = jsonExtract(prefs, "rotate").toInt();
// process camera settings
s->set_framesize(s, (framesize_t)jsonExtract(prefs, "framesize").toInt());
s->set_quality(s, jsonExtract(prefs, "quality").toInt());
s->set_xclk(s, LEDC_TIMER_0, xclk);
s->set_brightness(s, jsonExtract(prefs, "brightness").toInt());
s->set_contrast(s, jsonExtract(prefs, "contrast").toInt());
s->set_saturation(s, jsonExtract(prefs, "saturation").toInt());
s->set_special_effect(s, jsonExtract(prefs, "special_effect").toInt());
s->set_wb_mode(s, jsonExtract(prefs, "wb_mode").toInt());
s->set_whitebal(s, jsonExtract(prefs, "awb").toInt());
s->set_awb_gain(s, jsonExtract(prefs, "awb_gain").toInt());
s->set_exposure_ctrl(s, jsonExtract(prefs, "aec").toInt());
s->set_aec2(s, jsonExtract(prefs, "aec2").toInt());
s->set_ae_level(s, jsonExtract(prefs, "ae_level").toInt());
s->set_aec_value(s, jsonExtract(prefs, "aec_value").toInt());
s->set_gain_ctrl(s, jsonExtract(prefs, "agc").toInt());
s->set_agc_gain(s, jsonExtract(prefs, "agc_gain").toInt());
s->set_gainceiling(s, (gainceiling_t)jsonExtract(prefs, "gainceiling").toInt());
s->set_bpc(s, jsonExtract(prefs, "bpc").toInt());
s->set_wpc(s, jsonExtract(prefs, "wpc").toInt());
s->set_raw_gma(s, jsonExtract(prefs, "raw_gma").toInt());
s->set_lenc(s, jsonExtract(prefs, "lenc").toInt());
s->set_vflip(s, jsonExtract(prefs, "vflip").toInt());
s->set_hmirror(s, jsonExtract(prefs, "hmirror").toInt());
s->set_dcw(s, jsonExtract(prefs, "dcw").toInt());
s->set_colorbar(s, jsonExtract(prefs, "colorbar").toInt());
// close the file
file.close();
dumpPrefs(SPIFFS);
} else {
Serial.printf("Preference file %s not found; using system defaults.\r\n", PREFERENCES_FILE);
}
}
void savePrefs(fs::FS &fs){
if (fs.exists(PREFERENCES_FILE)) {
Serial.printf("Updating %s\r\n", PREFERENCES_FILE);
} else {
Serial.printf("Creating %s\r\n", PREFERENCES_FILE);
}
File file = fs.open(PREFERENCES_FILE, FILE_WRITE);
static char json_response[1024];
sensor_t * s = esp_camera_sensor_get();
char * p = json_response;
*p++ = '{';
p+=sprintf(p, "\"lamp\":%i,", lampVal);
p+=sprintf(p, "\"autolamp\":%u,", autoLamp);
p+=sprintf(p, "\"framesize\":%u,", s->status.framesize);
p+=sprintf(p, "\"quality\":%u,", s->status.quality);
p+=sprintf(p, "\"xclk\":%u,", xclk);
p+=sprintf(p, "\"min_frame_time\":%d,", minFrameTime);
p+=sprintf(p, "\"brightness\":%d,", s->status.brightness);
p+=sprintf(p, "\"contrast\":%d,", s->status.contrast);
p+=sprintf(p, "\"saturation\":%d,", s->status.saturation);
p+=sprintf(p, "\"special_effect\":%u,", s->status.special_effect);
p+=sprintf(p, "\"wb_mode\":%u,", s->status.wb_mode);
p+=sprintf(p, "\"awb\":%u,", s->status.awb);
p+=sprintf(p, "\"awb_gain\":%u,", s->status.awb_gain);
p+=sprintf(p, "\"aec\":%u,", s->status.aec);
p+=sprintf(p, "\"aec2\":%u,", s->status.aec2);
p+=sprintf(p, "\"ae_level\":%d,", s->status.ae_level);
p+=sprintf(p, "\"aec_value\":%u,", s->status.aec_value);
p+=sprintf(p, "\"agc\":%u,", s->status.agc);
p+=sprintf(p, "\"agc_gain\":%u,", s->status.agc_gain);
p+=sprintf(p, "\"gainceiling\":%u,", s->status.gainceiling);
p+=sprintf(p, "\"bpc\":%u,", s->status.bpc);
p+=sprintf(p, "\"wpc\":%u,", s->status.wpc);
p+=sprintf(p, "\"raw_gma\":%u,", s->status.raw_gma);
p+=sprintf(p, "\"lenc\":%u,", s->status.lenc);
p+=sprintf(p, "\"vflip\":%u,", s->status.vflip);
p+=sprintf(p, "\"hmirror\":%u,", s->status.hmirror);
p+=sprintf(p, "\"dcw\":%u,", s->status.dcw);
p+=sprintf(p, "\"colorbar\":%u,", s->status.colorbar);
p+=sprintf(p, "\"rotate\":\"%d\"", myRotation);
*p++ = '}';
*p++ = 0;
file.print(json_response);
file.close();
dumpPrefs(SPIFFS);
}
void removePrefs(fs::FS &fs) {
if (fs.exists(PREFERENCES_FILE)) {
Serial.printf("Removing %s\r\n", PREFERENCES_FILE);
if (!fs.remove(PREFERENCES_FILE)) {
Serial.println("Error removing preferences");
}
} else {
Serial.println("No saved preferences file to remove");
}
}
void filesystemStart(){
Serial.println("Starting internal SPIFFS filesystem");
while ( !SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED) ) {
// if we sit in this loop something is wrong;
// if no existing spiffs partition exists one should be automagically created.
Serial.println("SPIFFS Mount failed, this can happen on first-run initialisation");
Serial.println("If it happens repeatedly check if a SPIFFS partition is present for your board?");
for (int i=0; i<10; i++) {
flashLED(100); // Show SPIFFS failure
delay(100);
}
delay(1000);
Serial.println("Retrying..");
}
listDir(SPIFFS, "/", 0);
}

14
ESP32_CAM_NEW/storage.h Normal file
View File

@@ -0,0 +1,14 @@
#include "FS.h"
#include "SPIFFS.h"
#define FORMAT_SPIFFS_IF_FAILED true
#define PREFERENCES_MAX_SIZE 500
#define PREFERENCES_FILE "/esp32cam-preferences.json"
extern void dumpPrefs(fs::FS &fs);
extern void loadPrefs(fs::FS &fs);
extern void removePrefs(fs::FS &fs);
extern void savePrefs(fs::FS &fs);
extern void filesystemStart();