diff --git a/esp32cam-demo.ino b/esp32cam-demo.ino new file mode 100644 index 0000000..301b63c --- /dev/null +++ b/esp32cam-demo.ino @@ -0,0 +1,505 @@ + /******************************************************************************************************************* + * + * ESP32Cam development board demo sketch using Arduino IDE + * Github: https://github.com/alanesq/ESP32Cam-demo + * + * Starting point sketch for projects using the esp32cam development board with the following features + * web server with live video streaming + * sd card support (using 1bit mode to free some io pins) + * io pins available for use are 13, 12(must be low at boot) + * flash led is still available for use on pin 4 + * + * + * - created using the Arduino IDE with ESP32 module installed (https://dl.espressif.com/dl/package_esp32_index.json) + * No additional libraries required + * + * + * + * Info on the esp32cam board: https://randomnerdtutorials.com/esp32-cam-video-streaming-face-recognition-arduino-ide/ + * + * + * + *******************************************************************************************************************/ + + +#include "esp_camera.h" // https://github.com/espressif/esp32-camera +#include +#include + + +// --------------------------------------------------------------- +// -SETTINGS +// --------------------------------------------------------------- + + // Wifi settings (enter your wifi network details) + const char* ssid = ""; + const char* password = ""; + + const String stitle = "ESP32Cam-demo"; // title of this sketch + const String sversion = "26Sep20"; // Sketch version + + const bool debugInfo = 1; // show additional debug info. on serial port (1=enabled) + + // Camera related + const bool flashRequired = 1; // If flash to be used when capturing image (1 = yes) + const framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_VGA; // Image resolution: + // default = "const framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_XGA" + // 160x120 (QQVGA), 128x160 (QQVGA2), 176x144 (QCIF), 240x176 (HQVGA), + // 320x240 (QVGA), 400x296 (CIF), 640x480 (VGA, default), 800x600 (SVGA), + // 1024x768 (XGA), 1280x1024 (SXGA), 1600x1200 (UXGA) + + const int TimeBetweenStatus = 600; // speed of flashing system running ok status light (milliseconds) + + const int indicatorLED = 33; // onboard status LED pin (33) + + const int brightLED = 4; // onboard flash LED pin (4) + + + +// ****************************************************************************************************************** + + +WebServer server(80); // serve web pages on port 80 + +#include "soc/soc.h" // Used to disable brownout detection on 5v power feed +#include "soc/rtc_cntl_reg.h" + +#include "SD_MMC.h" // sd card - see https://randomnerdtutorials.com/esp32-cam-take-photo-save-microsd-card/ +#include +#include // gives file access +#define SD_CS 5 // sd chip select pin = 5 + +// Define global variables: + uint32_t lastStatus = millis(); // last time status light changed status (to flash all ok led) + bool sdcardPresent; // flag if an sd card is detected + int imageCounter; // image file name counter + +// camera type settings (CAMERA_MODEL_AI_THINKER) + #define CAMERA_MODEL_AI_THINKER + #define PWDN_GPIO_NUM 32 // power to camera enable + #define RESET_GPIO_NUM -1 // -1 = not used + #define XCLK_GPIO_NUM 0 + #define SIOD_GPIO_NUM 26 // i2c sda + #define SIOC_GPIO_NUM 27 // i2c scl + #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 // vsync_pin + #define HREF_GPIO_NUM 23 // href_pin + #define PCLK_GPIO_NUM 22 // pixel_clock_pin + + camera_config_t config; + + + + +// ****************************************************************************************************************** + + +// --------------------------------------------------------------- +// -SETUP SETUP SETUP SETUP SETUP SETUP +// --------------------------------------------------------------- + +void setup() { + + // Serial communication + Serial.begin(115200); + + Serial.println(("\n\n\n---------------------------------------")); + Serial.println("Starting - " + stitle + " - " + sversion); + Serial.println(("---------------------------------------")); + + // Turn-off the 'brownout detector' + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); + + // Connect to wifi + Serial.println(); + Serial.println(); + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected."); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + server.begin(); + + // define web pages (call procedures when url requested) + server.on("/", handleRoot); // root page + server.on("/stream", handleStream); // stream live video + server.on("/photo", handlePhoto); // capture image and save to sd card + server.onNotFound(handleNotFound); // invalid url requested + + // Define io pins + pinMode(indicatorLED, OUTPUT); + digitalWrite(indicatorLED,HIGH); + pinMode(brightLED, OUTPUT); + digitalWrite(brightLED,LOW); + + // set up camera + Serial.print(("Initialising camera: ")); + if (setupCameraHardware()) Serial.println("OK"); + else { + Serial.println("Error!"); + showError(2); // critical error so stop and flash led + } + + // Configure sd card + if (!SD_MMC.begin("/sdcard", true)) { // if loading sd card fails + // note: ("/sdcard", true) = 1 wire - see: https://www.reddit.com/r/esp32/comments/d71es9/a_breakdown_of_my_experience_trying_to_talk_to_an/ + Serial.println("No SD Card detected"); + sdcardPresent = 0; // flag no sd card available + } else { + uint8_t cardType = SD_MMC.cardType(); + if (cardType == CARD_NONE) { // if invalid card found + Serial.println("SD Card type detect failed"); + sdcardPresent = 0; // flag no sd card available + } else { + sdcardPresent = 1; // valid sd card found + uint16_t SDfreeSpace = (uint64_t)(SD_MMC.totalBytes() - SD_MMC.usedBytes()) / (1024 * 1024); + Serial.println("SD Card found, free space = " + String(SDfreeSpace) + "MB"); + sdcardPresent = 1; // flag sd card available + } + } + fs::FS &fs = SD_MMC; // sd card file system + + // discover number of image files stored /img of sd card and set image file counter accordingly + if (sdcardPresent) { + int tq=fs.mkdir("/img"); // create the "/img" folder on sd card if not already there + if (!tq) Serial.println("Unable to create IMG folder on sd card"); + + File root = fs.open("/img"); + while (true) + { + File entry = root.openNextFile(); + if (! entry) break; + imageCounter ++; // increment image counter + entry.close(); + } + root.close(); + Serial.println("Image file count = " + String(imageCounter)); + } + + // re-define io pins as sd card config. seems to reset them + pinMode(brightLED, OUTPUT); + pinMode(indicatorLED, OUTPUT); + + if (!psramFound()) { + Serial.println("Warning: No PSRam found so defaulting to image size 'CIF'"); + framesize_t FRAME_SIZE_IMAGE = FRAMESIZE_CIF; + } + + Serial.println("Started..."); + +} // setup + + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// -LOOP LOOP LOOP LOOP LOOP LOOP LOOP +// ---------------------------------------------------------------- + + +void loop() { + + server.handleClient(); // handle any web page requests + + + + + + // If not in triggered state flash status light to show sketch is running ok + if ((unsigned long)(millis() - lastStatus) >= TimeBetweenStatus) { + lastStatus = millis(); // reset timer + digitalWrite(indicatorLED,!digitalRead(indicatorLED)); // flip indicator led status + } + +} // loop + + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// Flash indicator LED +// ---------------------------------------------------------------- + + +void flashLED(int reps) { + for(int x=0; x < reps; x++) { + digitalWrite(indicatorLED,LOW); + delay(1000); + digitalWrite(indicatorLED,HIGH); + delay(500); + } +} + + +// critical error - stop sketch and continually flash error status +void showError(int errorNo) { + while(1) { + flashLED(errorNo); + delay(4000); + } +} + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// save live camera image to sd card +// iTitle = file name to use +// ---------------------------------------------------------------- + + +void storeImage(String iTitle) { + + if (debugInfo) Serial.println("Storing image #" + String(imageCounter) + " to sd card"); + + fs::FS &fs = SD_MMC; // sd card file system + + // capture live image from camera + // cameraImageSettings(); // apply camera sensor settings (in camera.h) + if (flashRequired) digitalWrite(brightLED,HIGH); // turn flash on + camera_fb_t *fb = esp_camera_fb_get(); // capture image frame from camera + digitalWrite(brightLED,LOW); // turn flash off + if (!fb) { + Serial.println("Error: Camera capture failed"); + flashLED(3); + } + + // save the image to sd card + imageCounter ++; // increment image counter + String SDfilename = "/img/" + iTitle + ".jpg"; // build the image file name + File file = fs.open(SDfilename, FILE_WRITE); // create file on sd card + if (!file) { + Serial.println("Error: Failed to create file on sd-card: " + SDfilename); + flashLED(4); + } else { + if (file.write(fb->buf, fb->len)) { // File created ok so save image to it + if (debugInfo) Serial.println("Image saved to sd card"); + } else { + Serial.println("Error: failed to save image to sd card"); + flashLED(4); + } + file.close(); // close image file on sd card + } + esp_camera_fb_return(fb); // return frame so memory can be released + +} // storeImage + + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// Set up the camera +// ---------------------------------------------------------------- + + +bool setupCameraHardware() { + + config.ledc_channel = LEDC_CHANNEL_0; + config.ledc_timer = LEDC_TIMER_0; + config.pin_d0 = Y2_GPIO_NUM; + config.pin_d1 = Y3_GPIO_NUM; + config.pin_d2 = Y4_GPIO_NUM; + config.pin_d3 = Y5_GPIO_NUM; + config.pin_d4 = Y6_GPIO_NUM; + config.pin_d5 = Y7_GPIO_NUM; + config.pin_d6 = Y8_GPIO_NUM; + config.pin_d7 = Y9_GPIO_NUM; + config.pin_xclk = XCLK_GPIO_NUM; + config.pin_pclk = PCLK_GPIO_NUM; + config.pin_vsync = VSYNC_GPIO_NUM; + config.pin_href = HREF_GPIO_NUM; + config.pin_sscb_sda = SIOD_GPIO_NUM; + config.pin_sscb_scl = SIOC_GPIO_NUM; + config.pin_pwdn = PWDN_GPIO_NUM; + config.pin_reset = RESET_GPIO_NUM; + config.xclk_freq_hz = 20000000; // XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) + config.pixel_format = PIXFORMAT_JPEG; // PIXFORMAT_ + YUV422, GRAYSCALE, RGB565, JPEG, RGB888? + config.frame_size = FRAME_SIZE_IMAGE; // Image sizes: 160x120 (QQVGA), 128x160 (QQVGA2), 176x144 (QCIF), 240x176 (HQVGA), 320x240 (QVGA), + // 400x296 (CIF), 640x480 (VGA, default), 800x600 (SVGA), 1024x768 (XGA), 1280x1024 (SXGA), 1600x1200 (UXGA) + config.jpeg_quality = 5; // 0-63 lower number means higher quality + config.fb_count = 1; // if more than one, i2s runs in continuous mode. Use only with JPEG + + esp_err_t camerr = esp_camera_init(&config); // initialise the camera + if (camerr != ESP_OK) Serial.printf("ERROR: Camera init failed with error 0x%x", camerr); + + // cameraImageSettings(); // apply camera sensor settings + + return (camerr == ESP_OK); // return boolean result of camera initilisation +} + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// -root web page requested i.e. http://x.x.x.x/ +// ---------------------------------------------------------------- +// Info on using html see https://www.arduino.cc/en/Reference/Ethernet + +void handleRoot() { + + WiFiClient client = server.client(); // open link with client + String tstr; // temp store for building line of html + + // log page request including clients IP address + IPAddress cip = client.remoteIP(); + if (debugInfo) Serial.println("Root page requested from: " + String(cip[0]) +"." + String(cip[1]) + "." + String(cip[2]) + "." + String(cip[3])); + + // html header + client.write(" \n"); + + // html body + client.write("

Hello from esp32cam

\n"); + + // end html + client.write("\n"); + delay(3); + client.stop(); + +} // handleRoot + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// -photo save to sd card i.e. http://x.x.x.x/photo +// ---------------------------------------------------------------- + +void handlePhoto() { + + WiFiClient client = server.client(); // open link with client + String tstr; // temp store for building line of html + + // log page request including clients IP address + IPAddress cip = client.remoteIP(); + if (debugInfo) Serial.println("Photo to sd card requested from: " + String(cip[0]) +"." + String(cip[1]) + "." + String(cip[2]) + "." + String(cip[3])); + + // save an image to sd card + storeImage(String(imageCounter)); + + // html header + client.write(" \n"); + + // html body + // note: if the line of html is not just plain text it has to be first put in to 'tstr' then sent in this way + // I find it easier to use strings then convert but this is probably frowned upon ;-) + tstr = "

Image saved to sd card as image number " + String(imageCounter) + "

\n"; + client.write(tstr.c_str()); + + // end html + client.write("\n"); + delay(3); + client.stop(); + +} // handlePhoto + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// -invalid web page requested +// ---------------------------------------------------------------- + +void handleNotFound() { + + if (debugInfo) Serial.println("Invalid web page requested"); + String message = "File Not Found\n\n"; + message += "URI: "; + message += server.uri(); + message += "\nMethod: "; + message += ( server.method() == HTTP_GET ) ? "GET" : "POST"; + message += "\nArguments: "; + message += server.args(); + message += "\n"; + + for ( uint8_t i = 0; i < server.args(); i++ ) { + message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n"; + } + + server.send ( 404, "text/plain", message ); + message = ""; // clear variable + +} // handleNotFound + + +// ****************************************************************************************************************** + + +// ---------------------------------------------------------------- +// -stream requested i.e. http://x.x.x.x/stream +// ---------------------------------------------------------------- +// Sends cam stream - thanks to Uwe Gerlach for the code showing how to do this + +void handleStream(){ + + WiFiClient client = server.client(); // open link with client + + // log page request including clients IP address + IPAddress cip = client.remoteIP(); + if (debugInfo) Serial.println("Video stream requested from: " + String(cip[0]) +"." + String(cip[1]) + "." + String(cip[2]) + "." + String(cip[3])); + + // HTML used in the web page + const char HEADER[] = "HTTP/1.1 200 OK\r\n" \ + "Access-Control-Allow-Origin: *\r\n" \ + "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n"; + const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n"; // marks end of each image frame + const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: "; // marks start of image data + const int hdrLen = strlen(HEADER); // length of the stored text, used when sending to web page + const int bdrLen = strlen(BOUNDARY); + const int cntLen = strlen(CTNTTYPE); + + // temp stores + char buf[32]; + int s; + camera_fb_t * fb = NULL; + + // send html header + client.write(HEADER, hdrLen); + client.write(BOUNDARY, bdrLen); + + // send live images until client disconnects + while (true) + { + if (!client.connected()) break; + fb = esp_camera_fb_get(); // capture live image frame + s = fb->len; // store size of image (i.e. buffer length) + client.write(CTNTTYPE, cntLen); // send content type html (i.e. jpg image) + sprintf( buf, "%d\r\n\r\n", s ); // format the image's size as html + client.write(buf, strlen(buf)); // send image size + client.write((char *)fb->buf, s); // send the image data + client.write(BOUNDARY, bdrLen); // send html boundary see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type + esp_camera_fb_return(fb); // return frame so memory can be released + } + + if (debugInfo) Serial.println("Video stream stopped"); + delay(3); + client.stop(); + + +} // handleStream + + +// ****************************************************************************************************************** +// end