From 16838a9ff922b8c00e950a91ae57872f0fc07bce Mon Sep 17 00:00:00 2001 From: klp Date: Tue, 2 Jul 2024 00:32:12 +0200 Subject: [PATCH 1/2] change: optimized the firmware with Wifi Setup in AP Mode. The AP Mode is automaticaly active, if the camera can't connect to Wifi network. --- API.md | 2 +- access.cpp | 112 +++++++++++++++++++++++ access.h | 14 +++ app_httpd.cpp | 21 +++++ esp32-cam-webserver.ino | 145 +++++++++++------------------ index_other.h | 187 ++++++++++++++++++++++++++++++++++++++ myconfig.sample.h | 197 ---------------------------------------- 7 files changed, 387 insertions(+), 291 deletions(-) create mode 100644 access.cpp create mode 100644 access.h delete mode 100644 myconfig.sample.h diff --git a/API.md b/API.md index 563011e5..58cbd3c7 100644 --- a/API.md +++ b/API.md @@ -6,7 +6,7 @@ The WebUI and camera server communicate entirely via HTTP requests and responses ## URI's ### Http Port * `/` - Default index -* `/?view=full|simple|portal` - Go direct to specific index +* `/?view=full|simple|portal|access` - Go direct to specific index * `/capture` - Return a Jpeg snapshot image * `/status` - Returns a JSON string with all camera status / pairs listed * `/control?var=&val=` - Set `` to `` diff --git a/access.cpp b/access.cpp new file mode 100644 index 00000000..893afa8a --- /dev/null +++ b/access.cpp @@ -0,0 +1,112 @@ +#include "esp_camera.h" +#include "src/jsonlib/jsonlib.h" +#include "access.h" + +extern void flashLED(int flashtime); + +extern char access_ssid[]; +extern char access_password[]; + +/* + * Useful utility when debugging... + */ + +void dumpAccess(fs::FS &fs){ + if (fs.exists(ACCESS_FILE)) { + // Dump contents for debug + File file = fs.open(ACCESS_FILE, FILE_READ); + int countSize = 0; + while (file.available() && countSize <= ACCESS_MAX_SIZE) { + Serial.print(char(file.read())); + countSize++; + } + Serial.println(""); + file.close(); + } else { + Serial.printf("%s not found, nothing to dump.\r\n", ACCESS_FILE); + } +} + +int loadAccess(fs::FS &fs){ + if (fs.exists(ACCESS_FILE)) { + // read file into a string + String access_conf; + Serial.printf("Loading access from file %s\r\n", ACCESS_FILE); + File file = fs.open(ACCESS_FILE, FILE_READ); + if (!file) { + Serial.println("Failed to open access file for reading, maybe corrupt, removing"); + removeAccess(SPIFFS); + return 1; + } + size_t size = file.size(); + if (size > ACCESS_MAX_SIZE) { + Serial.println("Access file size is too large, maybe corrupt, removing"); + removeAccess(SPIFFS); + return 1; + } + while (file.available()) { + access_conf += char(file.read()); + if (access_conf.length() > size) { + // corrupted SPIFFS files can return data beyond their declared size. + Serial.println("Access file failed to load properly, appears to be corrupt, removing"); + removeAccess(SPIFFS); + return 1; + } + } + + String str; + char charArray[64] = {0}; + int len = 0; + char* token = NULL; + + // process camera settings + Serial.println("ssid"); + str = jsonExtract(access_conf, "ssid"); + str.toCharArray(access_ssid, 32); + Serial.println(access_ssid); + + Serial.println("password"); + str = jsonExtract(access_conf, "password"); + str.toCharArray(access_password, 32); + Serial.println(access_password); + + // close the file + file.close(); + dumpAccess(SPIFFS); + return 0; + } else { + Serial.printf("Access file %s not found; using system defaults.\r\n", ACCESS_FILE); + return 1; + } +} + +void saveAccess(fs::FS &fs){ + if (fs.exists(ACCESS_FILE)) { + Serial.printf("Updating %s\r\n", ACCESS_FILE); + } else { + Serial.printf("Creating %s\r\n", ACCESS_FILE); + } + + File file = fs.open(ACCESS_FILE, FILE_WRITE); + static char json_response[1024]; + char * p = json_response; + *p++ = '{'; + p+=sprintf(p, "\"ssid\":\"%s\",", access_ssid); + p+=sprintf(p, "\"password\":%s,", access_password); + *p++ = '}'; + *p++ = 0; + file.print(json_response); + file.close(); + dumpAccess(SPIFFS); +} + +void removeAccess(fs::FS &fs) { + if (fs.exists(ACCESS_FILE)) { + Serial.printf("Removing %s\r\n", ACCESS_FILE); + if (!fs.remove(ACCESS_FILE)) { + Serial.println("Error removing access"); + } + } else { + Serial.println("No saved access file to remove"); + } +} diff --git a/access.h b/access.h new file mode 100644 index 00000000..fbf6372b --- /dev/null +++ b/access.h @@ -0,0 +1,14 @@ +#include "FS.h" +#include "SPIFFS.h" + +#define FORMAT_SPIFFS_IF_FAILED true +#define ACCESS_MAX_SIZE 500 + +#define ACCESS_FILE "/esp32cam-access.json" + +extern void dumpAccess(fs::FS &fs); +extern int loadAccess(fs::FS &fs); +extern void removeAccess(fs::FS &fs); +extern void saveAccess(fs::FS &fs); + +extern void filesystemStart(); diff --git a/app_httpd.cpp b/app_httpd.cpp index f2b11a90..b3f69f8f 100644 --- a/app_httpd.cpp +++ b/app_httpd.cpp @@ -27,6 +27,7 @@ #include "src/favicons.h" #include "src/logo.h" #include "storage.h" +#include "access.h" // Functions from the main .ino extern void flashLED(int flashtime); @@ -67,6 +68,9 @@ extern char otaPassword[]; extern unsigned long xclk; extern int sensorPID; +extern char access_ssid[]; +extern char access_password[]; + typedef struct { httpd_req_t *req; size_t len; @@ -406,6 +410,17 @@ static esp_err_t cmd_handler(httpd_req_t *req){ setLamp(lampVal); } } + else if(!strcmp(variable, "ssid")) { + Serial.println("set ssid"); + strncpy(access_ssid, value, sizeof(value)); + } + else if(!strcmp(variable, "password")) { + Serial.println("set password"); + strncpy(access_password, value, sizeof(value)); + } + else if(!strcmp(variable, "save_access")) { + if (filesystem) saveAccess(SPIFFS); + } else if(!strcmp(variable, "save_prefs")) { if (filesystem) savePrefs(SPIFFS); } @@ -735,6 +750,12 @@ static esp_err_t index_handler(httpd_req_t *req){ 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 if(strncmp(view,"access", sizeof(view)) == 0) { + Serial.println("Access setup 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_access_html, index_access_html_len); } else { Serial.print("Unknown page requested: "); Serial.println(view); diff --git a/esp32-cam-webserver.ino b/esp32-cam-webserver.ino index 64d6483a..a2ea7ef2 100644 --- a/esp32-cam-webserver.ino +++ b/esp32-cam-webserver.ino @@ -8,6 +8,7 @@ #include "src/parsebytes.h" #include "time.h" #include +#include "access.h" /* This sketch is a extension/expansion/reork of the 'official' ESP32 Camera example @@ -36,17 +37,24 @@ * */ -// 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 +#warning "Using Defaults: Copy myconfig.sample.h to myconfig.h and edit that to use your own settings" +#define WIFI_AP_ENABLE +#define WIFI_AP_SSID "ESP32-CAM-CONNECT" +#define WIFI_AP_PW "InsecurePassword" +#define CAMERA_MODEL_AI_THINKER +#define CAM_NAME "ESP32 camera server" +#define MDNS_NAME "esp32-cam" +#define HTTP_PORT 80 +#define STREAM_PORT 81 +#define XCLK_FREQ_MHZ 8 +#define CAM_ROTATION 0 +#define MIN_FRAME_TIME 0 +#define WIFI_WATCHDOG 15000 + +//#define DEFAULT_INDEX_FULL + +struct station { char ssid[65]; char password[65]; bool dhcp;}; +struct station stationList[] = {{"SSID","Password", true}}; // Upstream version string #include "src/version.h" @@ -66,6 +74,9 @@ int sketchSize; int sketchSpace; String sketchMD5; +char access_ssid[32] = {0, }; +char access_password[32] = {0, }; + // 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; @@ -79,45 +90,14 @@ IPAddress gw; 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 +char myName[] = CAM_NAME; +char mdnsName[] = MDNS_NAME; +int httpPort = HTTP_PORT; +int streamPort = STREAM_PORT; // 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 +int firstStation = 0; // Select between full and simple index as the default. #if defined(DEFAULT_INDEX_FULL) @@ -151,23 +131,11 @@ 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 +unsigned long xclk = XCLK_FREQ_MHZ; -// 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 @@ -406,39 +374,6 @@ void StartCamera() { #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 } @@ -470,6 +405,10 @@ void WifiSetup() { char bestSSID[65] = ""; uint8_t bestBSSID[6]; if (stationCount > firstStation) { + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + delay(100); + // We have a list to scan Serial.printf("Scanning local Wifi Networks\r\n"); int stationsFound = WiFi.scanNetworks(); @@ -580,6 +519,20 @@ void WifiSetup() { } if (accesspoint && (WiFi.status() != WL_CONNECTED)) { + WiFi.mode(WIFI_AP); + WiFi.disconnect(); + delay(100); + + removeAccess(SPIFFS); + + #ifdef WIFI_AP_SSID + strncpy(stationList[0].ssid, WIFI_AP_SSID, sizeof(WIFI_AP_SSID)); + #endif + + #ifdef WIFI_AP_PW + strncpy(stationList[0].password, WIFI_AP_PW, sizeof(WIFI_AP_PW)); + #endif + // 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"); @@ -676,6 +629,12 @@ void setup() { if (filesystem) { delay(200); // a short delay to let spi bus settle after camera init loadPrefs(SPIFFS); + + if(!loadAccess(SPIFFS)) + { + strncpy(stationList[0].ssid, access_ssid, sizeof(access_ssid)); + strncpy(stationList[0].password, access_password, sizeof(access_password)); + } } else { Serial.println("No Internal Filesystem, cannot load or save preferences"); } diff --git a/index_other.h b/index_other.h index 1eda91e1..737d5f5b 100644 --- a/index_other.h +++ b/index_other.h @@ -1,3 +1,188 @@ +/* + * accessviewer + */ + +const uint8_t index_access_html[] = R"=====( + + + + + ESP32 Access Setup + + + + + + + +
+ +
+ +
+
+ + + +)====="; + +size_t index_access_html_len = sizeof(index_access_html)-1; + /* * simpleviewer and streamviewer */ @@ -483,6 +668,8 @@ const std::string portal_html = R"=====( + +
Camera Details
diff --git a/myconfig.sample.h b/myconfig.sample.h deleted file mode 100644 index 86efcafe..00000000 --- a/myconfig.sample.h +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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[] = {{"my_ssid","my_password", 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 ".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_SVGA - -// 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 From e7c626b10a5d18a2771fd10fe36a88da45d4559d Mon Sep 17 00:00:00 2001 From: klp Date: Tue, 2 Jul 2024 22:53:05 +0200 Subject: [PATCH 2/2] change: update README for new feature. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index faa576c6..d3e1f2ea 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ ## Taken from the ESP examples, and expanded This sketch is a extension/expansion/rework of the 'official' ESP32 Camera example sketch from Espressif: +## Wifi Config Extension by CreLab + +This project was forked by easytarget/esp32-cam-webserver and extended with a Wifi setup. This extension makes it possible to enter and use the access data for a Wifi connection in an AP mode. + +If the camera does not find a known wifi network, it is configured as an access point with the name "ESP32-CAM-CONNECT". In this mode, the Wi-Fi connection can be configured via the "Access Setup" in the portal. + +To do this, enter the SSID / password and click save. After a reboot, a connection to the respective Wifi is established. + https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/Camera/CameraWebServer But expanded with: