first commit
This commit is contained in:
845
ESP32_CAM_NEW/ESP32_CAM_NEW.ino
Normal file
845
ESP32_CAM_NEW/ESP32_CAM_NEW.ino
Normal 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
885
ESP32_CAM_NEW/app_httpd.cpp
Normal 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 °C, %i °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
244
ESP32_CAM_NEW/camera_pins.h
Normal 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
367
ESP32_CAM_NEW/css.h
Normal 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
515
ESP32_CAM_NEW/index_other.h
Normal 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">☰ </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.

Warning:
Built-In lamps can be Very Bright! Avoid looking directly at LED
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⚠</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>)=====";
|
||||
633
ESP32_CAM_NEW/index_ov2640.h
Normal file
633
ESP32_CAM_NEW/index_ov2640.h
Normal 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;">☰ Settings </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.

Warning:
Built-In lamps can be Very Bright! Avoid looking directly at LED
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%;">⚠</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
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
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
Increasing this will raise the camera framerate and capture speed

Raising too far will result in visual artifacts and/or incomplete frames
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° (Right)</option>
|
||||
<option value="0" selected="selected">0° (None)</option>
|
||||
<option value="-90">-90° (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
Higher settings reduce the frame rate
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;
|
||||
642
ESP32_CAM_NEW/index_ov3660.h
Normal file
642
ESP32_CAM_NEW/index_ov3660.h
Normal 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;">☰ Settings </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.

Warning:
Built-In lamps can be Very Bright! Avoid looking directly at LED
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%;">⚠</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
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
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
Increasing this will raise the camera framerate and capture speed

Raising too far will result in visual artifacts and/or incomplete frames
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° (Right)</option>
|
||||
<option value="0" selected="selected">0° (None)</option>
|
||||
<option value="-90">-90° (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
Higher settings reduce the frame rate
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
197
ESP32_CAM_NEW/myconfig.h
Normal 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
211
ESP32_CAM_NEW/storage.cpp
Normal 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
14
ESP32_CAM_NEW/storage.h
Normal 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();
|
||||
Reference in New Issue
Block a user