2020-11-21 00:05:00 +08:00
# include <esp_camera.h>
# include <esp_int_wdt.h>
# include <esp_task_wdt.h>
2019-11-15 00:01:37 +08:00
# include <WiFi.h>
2020-10-06 19:29:54 +08:00
# include <DNSServer.h>
2021-06-08 21:49:10 +08:00
# include <ArduinoOTA.h>
2020-10-29 15:13:08 +08:00
# include "src/parsebytes.h"
2021-06-09 02:42:14 +08:00
# include "time.h"
2022-03-09 21:48:46 +08:00
# include <ESPmDNS.h>
2019-11-15 00:01:37 +08:00
2020-11-21 00:05:00 +08:00
2019-11-18 09:43:07 +08:00
/* 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
2020-10-04 01:49:08 +08:00
*
2019-11-18 09:43:07 +08:00
* 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
2020-10-04 01:49:08 +08:00
* for easy modification .
*
2022-03-09 15:40:00 +08:00
* A camera name can now be configured , and wifi details can be stored in an optional
2019-11-18 09:43:07 +08:00
* header file to allow easier updated of the repo .
2020-10-04 01:49:08 +08:00
*
* The web UI has had changes to add the lamp control , rotation , a standalone viewer ,
* more feeedback , new controls and other tweaks and changes ,
2019-11-18 09:43:07 +08:00
* note : Make sure that you have either selected ESP32 AI Thinker ,
2019-11-19 00:25:18 +08:00
* or another board which has PSRAM enabled to use high resolution camera modes
2020-08-24 03:49:10 +08:00
*/
2019-11-15 00:01:37 +08:00
2022-03-09 15:40:00 +08:00
/*
2020-10-04 01:49:08 +08:00
* FOR NETWORK AND HARDWARE SETTINGS COPY OR RENAME ' myconfig . sample . h ' TO ' myconfig . h ' AND EDIT THAT .
2020-08-24 03:49:10 +08:00
*
2020-10-06 19:29:54 +08:00
* By default this sketch will assume an AI - THINKER ESP - CAM and create
2020-08-24 03:49:10 +08:00
* an accesspoint called " ESP32-CAM-CONNECT " ( password : " InsecurePassword " )
*
*/
2019-11-15 00:01:37 +08:00
2020-09-09 08:57:37 +08:00
// Primary config, or defaults.
2019-11-18 17:31:48 +08:00
# if __has_include("myconfig.h")
2020-10-29 15:13:08 +08:00
struct station { const char ssid [ 65 ] ; const char password [ 65 ] ; const bool dhcp ; } ; // do no edit
2020-10-06 19:29:54 +08:00
# include "myconfig.h"
2019-11-15 00:01:37 +08:00
# else
2020-10-06 19:29:54 +08:00
# 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
2022-03-09 15:40:00 +08:00
struct station { const char ssid [ 65 ] ; const char password [ 65 ] ; const bool dhcp ; }
2020-10-06 19:29:54 +08:00
stationList [ ] = { { " ESP32-CAM-CONNECT " , " InsecurePassword " , true } } ;
2019-11-15 00:01:37 +08:00
# endif
2020-10-14 19:40:08 +08:00
// Upstream version string
2020-10-12 19:17:21 +08:00
# include "src/version.h"
2020-09-09 08:57:37 +08:00
// Pin Mappings
2020-09-26 18:19:46 +08:00
# include "camera_pins.h"
2020-09-09 08:57:37 +08:00
2020-09-27 21:21:44 +08:00
// Internal filesystem (SPIFFS)
2021-06-08 21:49:10 +08:00
// used for non-volatile camera settings
2020-09-27 21:21:44 +08:00
# include "storage.h"
2020-10-04 01:49:08 +08:00
// Sketch Info
int sketchSize ;
int sketchSpace ;
String sketchMD5 ;
2020-10-06 19:29:54 +08:00
// 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
2020-10-04 01:49:08 +08:00
IPAddress ip ;
IPAddress net ;
IPAddress gw ;
2020-09-09 08:57:37 +08:00
// Declare external function from app_httpd.cpp
2020-09-22 18:03:18 +08:00
extern void startCameraServer ( int hPort , int sPort ) ;
2021-05-09 18:45:10 +08:00
extern void serialDump ( ) ;
2020-09-09 08:57:37 +08:00
// A Name for the Camera. (set in myconfig.h)
# if defined(CAM_NAME)
2020-10-06 19:29:54 +08:00
char myName [ ] = CAM_NAME ;
2019-11-18 09:43:07 +08:00
# else
2020-10-06 19:29:54 +08:00
char myName [ ] = " ESP32 camera server " ;
2019-11-18 09:43:07 +08:00
# endif
2020-09-09 08:57:37 +08:00
// Ports for http and stream (override in myconfig.h)
# if defined(HTTP_PORT)
2020-10-06 19:29:54 +08:00
int httpPort = HTTP_PORT ;
2020-09-09 08:57:37 +08:00
# else
2020-10-06 19:29:54 +08:00
int httpPort = 80 ;
2020-09-09 08:57:37 +08:00
# endif
# if defined(STREAM_PORT)
2020-10-06 19:29:54 +08:00
int streamPort = STREAM_PORT ;
2020-09-09 08:57:37 +08:00
# else
2020-10-06 19:29:54 +08:00
int streamPort = 81 ;
2020-09-09 08:57:37 +08:00
# endif
2020-09-26 00:01:47 +08:00
# if !defined(WIFI_WATCHDOG)
2021-12-31 09:47:58 +08:00
# define WIFI_WATCHDOG 15000
2020-10-06 19:29:54 +08:00
# 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)
2022-03-09 15:40:00 +08:00
int firstStation = 1 ;
2020-10-06 19:29:54 +08:00
# else
int firstStation = 0 ;
2020-09-26 00:01:47 +08:00
# endif
2021-05-12 02:09:17 +08:00
// Select between full and simple index as the default.
2020-10-07 21:43:28 +08:00
# if defined(DEFAULT_INDEX_FULL)
char default_index [ ] = " full " ;
# else
char default_index [ ] = " simple " ;
# endif
2020-10-06 19:29:54 +08:00
// DNS server
const byte DNS_PORT = 53 ;
DNSServer dnsServer ;
bool captivePortal = false ;
char apName [ 64 ] = " Undefined " ;
2020-10-07 18:19:19 +08:00
// The app and stream URLs
char httpURL [ 64 ] = { " Undefined " } ;
char streamURL [ 64 ] = { " Undefined " } ;
2020-09-09 08:57:37 +08:00
2021-05-09 18:45:10 +08:00
// 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
2020-10-26 07:04:21 +08:00
2019-11-18 09:43:07 +08:00
// This will be displayed to identify the firmware
char myVer [ ] PROGMEM = __DATE__ " @ " __TIME__ ;
2021-07-03 22:10:37 +08:00
// Camera module bus communications frequency.
2022-03-08 18:34:45 +08:00
// Originally: config.xclk_freq_mhz = 20000000, but this lead to visual artifacts on many modules.
2021-07-03 22:10:37 +08:00
// See https://github.com/espressif/esp32-camera/issues/150#issuecomment-726473652 et al.
2022-03-08 18:34:45 +08:00
# if !defined (XCLK_FREQ_MHZ)
unsigned long xclk = 16 ;
2021-12-31 09:47:58 +08:00
# else
2022-03-08 18:34:45 +08:00
unsigned long xclk = XCLK_FREQ_MHZ ;
2021-07-03 22:10:37 +08:00
# endif
2020-09-22 16:55:40 +08:00
// initial rotation
// can be set in myconfig.h
# if !defined(CAM_ROTATION)
2020-10-06 19:29:54 +08:00
# define CAM_ROTATION 0
2020-09-22 16:55:40 +08:00
# endif
int myRotation = CAM_ROTATION ;
2019-11-18 09:43:07 +08:00
2021-12-17 05:14:44 +08:00
// minimal frame duration in ms, effectively 1/maxFPS
2021-12-18 19:29:43 +08:00
# if !defined(MIN_FRAME_TIME)
# define MIN_FRAME_TIME 0
2021-12-17 05:14:44 +08:00
# endif
2021-12-18 19:29:43 +08:00
int minFrameTime = MIN_FRAME_TIME ;
2021-12-17 05:14:44 +08:00
2021-09-26 19:02:49 +08:00
// Illumination LAMP and status LED
2020-09-09 08:57:37 +08:00
# if defined(LAMP_DISABLE)
2020-10-06 19:29:54 +08:00
int lampVal = - 1 ; // lamp is disabled in config
2020-09-09 08:57:37 +08:00
# elif defined(LAMP_PIN)
2020-10-06 19:29:54 +08:00
# 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
2022-03-09 15:40:00 +08:00
# else
2020-10-06 19:29:54 +08:00
int lampVal = - 1 ; // no lamp pin assigned
2020-10-04 01:49:08 +08:00
# endif
2021-09-26 19:02:49 +08:00
# if defined(LED_DISABLE)
# undef LED_PIN // undefining this disables the notification LED
# endif
2020-10-26 07:04:21 +08:00
bool autoLamp = false ; // Automatic lamp (auto on while camera running)
2020-08-24 03:49:10 +08:00
int lampChannel = 7 ; // a free PWM channel (some channels used by camera)
2019-11-16 22:24:25 +08:00
const int pwmfreq = 50000 ; // 50K pwm frequency
2019-11-17 21:06:41 +08:00
const int pwmresolution = 9 ; // duty cycle bit range
2020-09-09 08:57:37 +08:00
const int pwmMax = pow ( 2 , pwmresolution ) - 1 ;
2020-10-06 19:29:54 +08:00
# if defined(NO_FS)
bool filesystem = false ;
2020-09-27 21:21:44 +08:00
# else
2020-10-06 19:29:54 +08:00
bool filesystem = true ;
2020-09-27 21:21:44 +08:00
# endif
2021-06-08 21:49:10 +08:00
# if defined(NO_OTA)
bool otaEnabled = false ;
2020-09-09 08:57:37 +08:00
# else
2021-06-08 21:49:10 +08:00
bool otaEnabled = true ;
2020-09-09 08:57:37 +08:00
# endif
2019-11-16 22:24:25 +08:00
2021-09-03 20:13:40 +08:00
# if defined(OTA_PASSWORD)
char otaPassword [ ] = OTA_PASSWORD ;
# else
char otaPassword [ ] = " " ;
# endif
2021-06-09 02:42:14 +08:00
# 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
2021-05-12 02:09:17 +08:00
2020-11-21 00:05:00 +08:00
// Critical error string; if set during init (camera hardware failure) it
// will be returned for all http requests
String critERR = " " ;
2021-05-09 21:04:00 +08:00
// 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
}
2020-10-12 00:19:22 +08:00
2022-03-09 15:40:00 +08:00
// Notification LED
2020-09-17 04:12:41 +08:00
void flashLED ( int flashtime ) {
# ifdef LED_PIN // If we have it; flash it.
2020-10-06 19:29:54 +08:00
digitalWrite ( LED_PIN , LED_ON ) ; // On at full power.
delay ( flashtime ) ; // delay
digitalWrite ( LED_PIN , LED_OFF ) ; // turn Off
2020-09-17 04:12:41 +08:00
# else
2020-10-06 19:29:54 +08:00
return ; // No notifcation LED, do nothing, no delay
2020-09-17 04:12:41 +08:00
# endif
2020-11-21 00:05:00 +08:00
}
2020-09-17 04:12:41 +08:00
// Lamp Control
void setLamp ( int newVal ) {
2020-10-06 19:29:54 +08:00
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 ) ;
}
2020-09-26 00:01:47 +08:00
}
2021-06-09 02:42:14 +08:00
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 ) ;
}
}
2021-09-11 21:17:41 +08:00
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
}
2020-10-06 19:29:54 +08:00
void WifiSetup ( ) {
// Feedback that we are now attempting to connect
flashLED ( 300 ) ;
delay ( 100 ) ;
flashLED ( 300 ) ;
2020-10-07 21:43:28 +08:00
Serial . println ( " Starting WiFi " ) ;
2021-06-08 16:57:40 +08:00
2022-03-09 15:40:00 +08:00
// Disable power saving on WiFi to improve responsiveness
2021-06-08 16:57:40 +08:00
// (https://github.com/espressif/arduino-esp32/issues/1484)
WiFi . setSleep ( false ) ;
2020-10-07 21:43:28 +08:00
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 ( ) ;
2020-10-29 15:13:08 +08:00
byte mac [ 6 ] = { 0 , 0 , 0 , 0 , 0 , 0 } ;
2020-10-07 21:43:28 +08:00
WiFi . macAddress ( mac ) ;
2021-05-12 02:09:17 +08:00
Serial . printf ( " MAC address: %02X:%02X:%02X:%02X:%02X:%02X \r \n " , mac [ 0 ] , mac [ 1 ] , mac [ 2 ] , mac [ 3 ] , mac [ 4 ] , mac [ 5 ] ) ;
2022-03-09 15:40:00 +08:00
2020-10-06 19:29:54 +08:00
int bestStation = - 1 ;
2020-10-07 21:43:28 +08:00
long bestRSSI = - 1024 ;
2020-10-29 15:13:08 +08:00
char bestSSID [ 65 ] = " " ;
uint8_t bestBSSID [ 6 ] ;
2020-10-07 21:43:28 +08:00
if ( stationCount > firstStation ) {
2022-03-09 15:40:00 +08:00
// We have a list to scan
2021-05-12 02:09:17 +08:00
Serial . printf ( " Scanning local Wifi Networks \r \n " ) ;
2020-10-07 21:43:28 +08:00
int stationsFound = WiFi . scanNetworks ( ) ;
2021-05-12 02:09:17 +08:00
Serial . printf ( " %i networks found \r \n " , stationsFound ) ;
2020-10-07 21:43:28 +08:00
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 ) ;
2020-10-29 15:13:08 +08:00
String thisBSSID = WiFi . BSSIDstr ( i ) ;
Serial . printf ( " %3i : [%s] %s (%i) " , i + 1 , thisBSSID . c_str ( ) , thisSSID . c_str ( ) , thisRSSI ) ;
2020-10-07 21:43:28 +08:00
// Scan our list of known external stations
2020-10-06 19:29:54 +08:00
for ( int sta = firstStation ; sta < stationCount ; sta + + ) {
2022-03-09 15:40:00 +08:00
if ( ( strcmp ( stationList [ sta ] . ssid , thisSSID . c_str ( ) ) = = 0 ) | |
2020-10-29 15:13:08 +08:00
( strcmp ( stationList [ sta ] . ssid , thisBSSID . c_str ( ) ) = = 0 ) ) {
2020-10-06 19:29:54 +08:00
Serial . print ( " - Known! " ) ;
// Chose the strongest RSSI seen
if ( thisRSSI > bestRSSI ) {
bestStation = sta ;
2020-10-29 15:13:08 +08:00
strncpy ( bestSSID , thisSSID . c_str ( ) , 64 ) ;
// Convert char bssid[] to a byte array
2022-03-09 15:40:00 +08:00
parseBytes ( thisBSSID . c_str ( ) , ' : ' , bestBSSID , 6 , 16 ) ;
2020-10-06 19:29:54 +08:00
bestRSSI = thisRSSI ;
}
}
}
2020-10-07 21:43:28 +08:00
Serial . println ( ) ;
2020-10-06 19:29:54 +08:00
}
}
2020-10-07 21:43:28 +08:00
} else {
// No list to scan, therefore we are an accesspoint
2022-03-09 15:40:00 +08:00
accesspoint = true ;
2020-10-06 19:29:54 +08:00
}
2020-10-07 21:43:28 +08:00
2020-10-06 19:29:54 +08:00
if ( bestStation = = - 1 ) {
2022-03-09 15:40:00 +08:00
if ( ! accesspoint ) {
2020-10-07 21:43:28 +08:00
# 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 " ) ;
}
2020-10-06 19:29:54 +08:00
} else {
2022-03-09 15:40:00 +08:00
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 ] ,
2020-10-29 15:13:08 +08:00
bestBSSID [ 4 ] , bestBSSID [ 5 ] , bestSSID ) ;
// Apply static settings if necesscary
2020-10-06 19:29:54 +08:00
if ( stationList [ bestStation ] . dhcp = = false ) {
# if defined(ST_IP)
Serial . println ( " Applying static IP settings " ) ;
2022-03-09 15:40:00 +08:00
# if !defined (ST_GATEWAY) || !defined (ST_NETMASK)
2020-10-06 19:29:54 +08:00
# 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
}
2020-10-26 07:04:21 +08:00
# if defined(HOSTNAME)
WiFi . setHostname ( HOSTNAME ) ;
# endif
2020-10-29 15:13:08 +08:00
// Initiate network connection request (3rd argument, channel = 0 is 'auto')
WiFi . begin ( bestSSID , stationList [ bestStation ] . password , 0 , bestBSSID ) ;
2020-10-26 07:04:21 +08:00
2020-10-06 19:29:54 +08:00
// Wait to connect, or timeout
2022-03-09 15:40:00 +08:00
unsigned long start = millis ( ) ;
2020-10-06 19:29:54 +08:00
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 ( ) ;
2021-05-12 02:09:17 +08:00
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 ] ) ;
2021-09-11 21:17:41 +08:00
calcURLs ( ) ;
2020-10-06 19:29:54 +08:00
// 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)
}
}
2020-10-07 21:43:28 +08:00
2020-10-06 19:29:54 +08:00
if ( accesspoint & & ( WiFi . status ( ) ! = WL_CONNECTED ) ) {
2020-10-07 21:43:28 +08:00
// The accesspoint has been enabled, and we have not connected to any existing networks
2020-10-06 19:29:54 +08:00
# 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 ) ;
2021-05-12 02:09:17 +08:00
Serial . printf ( " IP address: %d.%d.%d.%d \r \n " , ip [ 0 ] , ip [ 1 ] , ip [ 2 ] , ip [ 3 ] ) ;
2021-09-11 21:17:41 +08:00
calcURLs ( ) ;
2020-10-06 19:29:54 +08:00
// 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 ;
}
2020-10-29 15:13:08 +08:00
}
2020-10-06 19:29:54 +08:00
}
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 ) ;
2020-10-12 19:17:21 +08:00
Serial . print ( " Base Release: " ) ;
Serial . println ( baseVersion ) ;
2022-03-08 21:56:25 +08:00
Serial . println ( ) ;
2020-10-06 19:29:54 +08:00
2022-03-08 20:46:51 +08:00
// Warn if no PSRAM is detected (typically user error with board selection in the IDE)
if ( ! psramFound ( ) ) {
Serial . println ( " \r \n Fatal Error; Halting " ) ;
while ( true ) {
Serial . println ( " No PSRAM found; camera cannot be initialised: Please check the board config for your module. " ) ;
delay ( 5000 ) ;
}
}
2020-10-06 19:29:54 +08:00
if ( stationCount = = 0 ) {
2022-03-08 20:46:51 +08:00
Serial . println ( " \r \n Fatal 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 ) ;
}
2020-10-06 19:29:54 +08:00
}
2020-10-26 07:04:21 +08:00
2020-10-06 19:29:54 +08:00
# if defined(LED_PIN) // If we have a notification LED, set it to output
pinMode ( LED_PIN , OUTPUT ) ;
digitalWrite ( LED_PIN , LED_ON ) ;
# endif
2022-03-08 21:56:25 +08:00
// 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
}
2022-03-09 15:40:00 +08:00
// Create camera config structure; and populate with hardware and other defaults
2020-10-06 19:29:54 +08:00
camera_config_t config ;
config . ledc_channel = LEDC_CHANNEL_0 ;
config . ledc_timer = LEDC_TIMER_0 ;
config . pin_d0 = Y2_GPIO_NUM ;
config . pin_d1 = Y3_GPIO_NUM ;
config . pin_d2 = Y4_GPIO_NUM ;
config . pin_d3 = Y5_GPIO_NUM ;
config . pin_d4 = Y6_GPIO_NUM ;
config . pin_d5 = Y7_GPIO_NUM ;
config . pin_d6 = Y8_GPIO_NUM ;
config . pin_d7 = Y9_GPIO_NUM ;
config . pin_xclk = XCLK_GPIO_NUM ;
config . pin_pclk = PCLK_GPIO_NUM ;
config . pin_vsync = VSYNC_GPIO_NUM ;
config . pin_href = HREF_GPIO_NUM ;
config . pin_sscb_sda = SIOD_GPIO_NUM ;
config . pin_sscb_scl = SIOC_GPIO_NUM ;
config . pin_pwdn = PWDN_GPIO_NUM ;
config . pin_reset = RESET_GPIO_NUM ;
2022-03-08 18:34:45 +08:00
config . xclk_freq_hz = xclk * 1000000 ;
2020-10-06 19:29:54 +08:00
config . pixel_format = PIXFORMAT_JPEG ;
2021-12-31 09:47:58 +08:00
config . grab_mode = CAMERA_GRAB_LATEST ;
2021-07-01 01:56:08 +08:00
// Pre-allocate large buffers
2020-10-06 19:29:54 +08:00
if ( psramFound ( ) ) {
config . frame_size = FRAMESIZE_UXGA ;
config . jpeg_quality = 10 ;
2021-12-31 09:47:58 +08:00
config . fb_count = 2 ;
2020-10-06 19:29:54 +08:00
} else {
config . frame_size = FRAMESIZE_SVGA ;
config . jpeg_quality = 12 ;
config . fb_count = 1 ;
}
# 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 ) ;
2020-11-21 00:05:00 +08:00
if ( err ! = ESP_OK ) {
2020-10-06 19:29:54 +08:00
delay ( 100 ) ; // need a delay here or the next serial o/p gets missed
2021-05-12 02:09:17 +08:00
Serial . printf ( " \r \n \r \n CRITICAL 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 " ) ;
2020-11-21 00:05:00 +08:00
// 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 ) ;
2022-03-09 15:40:00 +08:00
esp_task_wdt_add ( NULL ) ;
2020-11-21 00:05:00 +08:00
} else {
Serial . println ( " Camera init succeeded " ) ;
2020-10-06 19:29:54 +08:00
2020-11-21 00:05:00 +08:00
// Get a reference to the sensor
sensor_t * s = esp_camera_sensor_get ( ) ;
2020-10-06 19:29:54 +08:00
2020-11-21 00:05:00 +08:00
// Dump camera module, warn for unsupported modules.
switch ( s - > id . PID ) {
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 " ) ;
}
2020-10-06 19:29:54 +08:00
2020-11-21 00:05:00 +08:00
// OV3660 initial sensors are flipped vertically and colors are a bit saturated
if ( s - > id . PID = = 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
}
2020-09-26 00:01:47 +08:00
2020-11-21 00:05:00 +08:00
// M5 Stack Wide has special needs
# if defined(CAMERA_MODEL_M5STACK_WIDE)
s - > set_vflip ( s , 1 ) ;
s - > set_hmirror ( s , 1 ) ;
# endif
2020-09-26 00:01:47 +08:00
2020-11-21 00:05:00 +08:00
// 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
2020-10-06 19:29:54 +08:00
2020-11-21 00:05:00 +08:00
// set initial frame rate
# if defined(DEFAULT_RESOLUTION)
s - > set_framesize ( s , DEFAULT_RESOLUTION ) ;
# else
s - > set_framesize ( s , FRAMESIZE_SVGA ) ;
# endif
/*
* Add any other defaults you want to apply at startup here :
* uncomment the line and set the value as desired ( see the comments )
2022-03-09 15:40:00 +08:00
*
2020-11-21 00:05:00 +08:00
* 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
// check for saved preferences and apply them
if ( filesystem ) {
2021-12-31 09:47:58 +08:00
delay ( 200 ) ; // a short delay to let spi bus settle after camera init
2020-11-21 00:05:00 +08:00
loadPrefs ( SPIFFS ) ;
} else {
2021-09-26 17:27:05 +08:00
Serial . println ( " No Internal Filesystem, cannot load or save preferences " ) ;
2020-11-21 00:05:00 +08:00
}
2020-09-26 00:01:47 +08:00
}
2020-10-04 01:49:08 +08:00
2020-10-26 07:04:21 +08:00
/*
2020-10-06 19:29:54 +08:00
* Camera setup complete ; initialise the rest of the hardware .
*/
2022-03-09 18:40:40 +08:00
// Start Wifi and loop until we are connected or have started an AccessPoint
2020-10-06 19:29:54 +08:00
while ( ( WiFi . status ( ) ! = WL_CONNECTED ) & & ! accesspoint ) {
WifiSetup ( ) ;
delay ( 1000 ) ;
2021-06-08 21:49:10 +08:00
}
2022-03-09 18:40:40 +08:00
// Set up OTA
2021-06-08 21:49:10 +08:00
if ( otaEnabled ) {
// Start OTA once connected
Serial . println ( " Setting up OTA " ) ;
// Port defaults to 3232
2022-03-09 15:40:00 +08:00
// ArduinoOTA.setPort(3232);
2021-06-08 21:49:10 +08:00
// Hostname defaults to esp3232-[MAC]
ArduinoOTA . setHostname ( myName ) ;
// No authentication by default
2021-09-03 20:13:40 +08:00
if ( strlen ( otaPassword ) ! = 0 ) {
ArduinoOTA . setPassword ( otaPassword ) ;
Serial . printf ( " OTA Password: %s \n \r " , otaPassword ) ;
} else {
2021-12-16 23:14:30 +08:00
Serial . printf ( " \r \n No OTA password has been set! (insecure) \r \n \r \n " ) ;
2021-09-03 20:13:40 +08:00
}
2021-06-08 21:49:10 +08:00
ArduinoOTA
. onStart ( [ ] ( ) {
String type ;
if ( ArduinoOTA . getCommand ( ) = = U_FLASH )
type = " sketch " ;
else // U_SPIFFS
type = " filesystem " ;
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial . println ( " Start updating " + type ) ;
} )
. onEnd ( [ ] ( ) {
2021-12-16 23:14:30 +08:00
Serial . println ( " \r \n End " ) ;
2021-06-08 21:49:10 +08:00
} )
. 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 " ) ;
2022-03-09 21:48:46 +08:00
if ( ! MDNS . begin ( myName ) ) {
Serial . println ( " Error setting up MDNS responder! " ) ;
}
Serial . println ( " mDNS responder started " ) ;
2021-06-08 21:49:10 +08:00
}
2020-10-06 19:29:54 +08:00
2022-03-09 21:48:46 +08:00
//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 " ) ;
2021-06-09 02:42:14 +08:00
// 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 " ) ;
}
2022-03-09 18:40:40 +08:00
// 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 ) {
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 ) ;
} else {
Serial . println ( " No lamp, or lamp disabled in config " ) ;
}
// Start the camera server
2020-10-06 19:29:54 +08:00
startCameraServer ( httpPort , streamPort ) ;
2020-10-26 07:04:21 +08:00
2020-11-21 00:05:00 +08:00
if ( critERR . length ( ) = = 0 ) {
2021-05-12 02:09:17 +08:00
Serial . printf ( " \r \n Camera Ready! \r \n Use '%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 ) ;
2021-05-09 21:04:00 +08:00
# if defined(DEBUG_DEFAULT_ON)
debugOn ( ) ;
# else
debugOff ( ) ;
# endif
2020-11-21 00:05:00 +08:00
} else {
2021-05-12 02:09:17 +08:00
Serial . printf ( " \r \n Camera unavailable due to initialisation errors. \r \n \r \n " ) ;
2020-11-21 00:05:00 +08:00
}
2020-10-12 00:19:22 +08:00
2022-03-09 18:40:40 +08:00
// 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");
2021-05-12 02:09:17 +08:00
// 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 ( ) ;
2019-11-15 00:01:37 +08:00
}
void loop ( ) {
2022-03-09 15:40:00 +08:00
/*
2020-10-06 19:29:54 +08:00
* 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
2021-05-09 21:04:00 +08:00
// Rather than loop forever, follow the watchdog, in case we later add auto re-scan.
2020-10-06 19:29:54 +08:00
unsigned long start = millis ( ) ;
while ( millis ( ) - start < WIFI_WATCHDOG ) {
delay ( 100 ) ;
2021-06-08 21:49:10 +08:00
if ( otaEnabled ) ArduinoOTA . handle ( ) ;
2021-05-09 21:04:00 +08:00
handleSerial ( ) ;
2020-10-06 19:29:54 +08:00
if ( captivePortal ) dnsServer . processNextRequest ( ) ;
}
2020-09-26 00:01:47 +08:00
} else {
2022-03-09 15:40:00 +08:00
// client mode can fail; so reconnect as appropriate
2020-10-06 19:29:54 +08:00
static bool warned = false ;
if ( WiFi . status ( ) = = WL_CONNECTED ) {
// We are connected, wait a bit and re-check
if ( warned ) {
2022-03-09 15:40:00 +08:00
// Tell the user if we have just reconnected
2020-10-06 19:29:54 +08:00
Serial . println ( " WiFi reconnected " ) ;
warned = false ;
}
2020-10-12 00:19:22 +08:00
// loop here for WIFI_WATCHDOG, turning debugData true/false depending on serial input..
unsigned long start = millis ( ) ;
while ( millis ( ) - start < WIFI_WATCHDOG ) {
delay ( 100 ) ;
2021-06-08 21:49:10 +08:00
if ( otaEnabled ) ArduinoOTA . handle ( ) ;
2021-05-09 21:04:00 +08:00
handleSerial ( ) ;
2020-10-12 00:19:22 +08:00
}
2020-10-06 19:29:54 +08:00
} 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 ( ) ;
}
2020-09-26 00:01:47 +08:00
}
2019-11-15 00:01:37 +08:00
}