Last active
November 27, 2018 15:23
-
-
Save utarn/28286d2f0a663279bc5c716f5cab2898 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ESP8266-12E ArduCAM Mini Camera Server | |
// | |
// This program demonstrates using an ArduCAM Mini 2MP camera with an ESP8266-12E module. | |
// An OV2640 2MP ArduCAM Mini was used with this program. | |
// | |
// The program has a web interface that will allow: | |
// - storing and viewing of captured jpg files | |
// - viewing live streamed video | |
// - changing the resolution | |
// | |
// Captured jpeg files are stored on the ESP8266-12E built in memory. | |
// | |
// The capture and streaming features can be reached directly via the browser by using the format: | |
// http://IPaddress/capture - for still capture | |
// http://IPaddress/stream - for streaming video | |
// | |
// The captured stills or streamed video can be accessed as an HTML image: | |
// <img src="http://IPaddress/capture"> | |
// or | |
// <img src="http://IPaddress/stream"> | |
// | |
// | |
// Wiring diagram to connect ArduCAM to ESP8266-12E | |
// | |
// ArduCAM mini -> ESP8266-12E | |
// CS -> D0 | |
// MOSI -> D7 | |
// MISC -> D6 | |
// SCK -> D5 | |
// GND -> GND | |
// VCC -> 3V3 | |
// SDA -> D2 | |
// SCL -> D1 | |
#include <FS.h> // FOR SPIFFS | |
#include <ESP8266WiFi.h> | |
#include <WiFiClient.h> | |
#include <ESP8266WebServer.h> | |
#include <Wire.h> | |
#include <ArduCAM.h> | |
#include <SPI.h> | |
#include "memorysaver.h" | |
#if !(defined ESP8266 ) | |
#error Please select the ArduCAM ESP8266 UNO board in the Tools/Board | |
#endif | |
//This demo can only work on OV2640_MINI_2MP or ARDUCAM_SHIELD_V2 platform. | |
#if !(defined (OV2640_MINI_2MP)||(defined (ARDUCAM_SHIELD_V2) && defined (OV2640_CAM))) | |
#error Please select the hardware platform and camera module in the ../libraries/ArduCAM/memorysaver.h file | |
#endif | |
// set GPIO16 as the slave select : | |
const int CS = 16; | |
// if the video is chopped or distored, try using a lower value for the buffer | |
// lower values will have fewer frames per second while streaming | |
static const size_t bufferSize = 4096; // 4096; //2048; //1024; | |
static const int fileSpaceOffset = 700000; | |
static const int wifiType = 0; // 0:Station 1:AP | |
//AP mode configuration | |
const char *AP_ssid = "arducam_esp8266"; | |
//Default is no password.If you want to set password,put your password here | |
const char *AP_password = "APpassword"; | |
//Station mode you should put your ssid and password | |
const char *ssid = "Eak sit_2.4GHz"; // Put your SSID here | |
const char *password = "0967066445"; // Put your PASSWORD here | |
static IPAddress ip(192, 168, 1, 8); // static IP used for browser access: http://IPaddress | |
static IPAddress gateway(192, 168, 1, 1); | |
static IPAddress subnet(255, 255, 255, 0);; | |
const String fName = "res.txt"; | |
int fileTotalKB = 0; | |
int fileUsedKB = 0; int fileCount = 0; | |
String errMsg = ""; | |
int imgMode = 1; // 0: stream 1: capture | |
int resolution = 3; | |
// resolutions: | |
// 0 = 160x120 | |
// 1 = 176x144 | |
// 2 = 320x240 | |
// 3 = 352x288 | |
// 4 = 640x480 | |
// 5 = 800x600 | |
// 6 = 1024x768 | |
// 7 = 1280x1024 | |
// 8 = 1600x1200 | |
ESP8266WebServer server(80); | |
ArduCAM myCAM(OV2640, CS); | |
///////////////////////////////////////////////// | |
// Updates Properties file with resolution /// | |
///////////////////////////////////////////////// | |
void updateDataFile() | |
{ | |
File f = SPIFFS.open(fName, "w"); | |
if (!f) { | |
Serial.println("prop file open failed"); | |
} | |
else | |
{ | |
Serial.println("====== Writing to prop file ========="); | |
f.println(resolution); | |
Serial.println("Data file updated"); | |
f.close(); | |
} | |
} | |
/////////////////////////////////////////// | |
// Saves captured image to memory // | |
/////////////////////////////////////////// | |
void myCAMSaveToSPIFFS() { | |
// as file space is used, capturing images will get slower. At a certain point, the images will become distored | |
// or they will not save at all due to lack of space. To avoid this we set a limit and allow some free space to remain | |
if ((fileTotalKB - fileUsedKB) < fileSpaceOffset) | |
{ | |
String maxStr = "====== Maximum Data Storage Reached ========="; | |
Serial.println(maxStr); | |
errMsg = maxStr; | |
return; | |
} | |
String str; | |
byte buf[256]; | |
static int i = 0; | |
static int n = 0; | |
uint8_t temp, temp_last; | |
// File file; | |
//Flush the FIFO | |
myCAM.flush_fifo(); | |
//Clear the capture done flag | |
myCAM.clear_fifo_flag(); | |
//Start capture | |
myCAM.start_capture(); | |
while (!myCAM.get_bit(ARDUCHIP_TRIG , CAP_DONE_MASK)); | |
Serial.println("File Capture Done!"); | |
fileCount++; | |
str = "/pics/" + String(fileCount) + ".jpg"; | |
File f = SPIFFS.open(str, "w"); | |
if (!f) { | |
Serial.println("prop file open failed"); | |
} | |
else | |
{ | |
Serial.println(str); | |
} | |
i = 0; | |
myCAM.CS_LOW(); | |
myCAM.set_fifo_burst(); | |
#if !(defined (ARDUCAM_SHIELD_V2) && defined (OV2640_CAM)) | |
SPI.transfer(0xFF); | |
#endif | |
//Read JPEG data from FIFO | |
while ( (temp != 0xD9) | (temp_last != 0xFF)) { | |
temp_last = temp; | |
temp = SPI.transfer(0x00); | |
//Write image data to buffer if not full | |
if ( i < 256) | |
buf[i++] = temp; | |
else { | |
//Write 256 bytes image data to file | |
myCAM.CS_HIGH(); | |
f.write(buf , 256); | |
i = 0; | |
buf[i++] = temp; | |
myCAM.CS_LOW(); | |
myCAM.set_fifo_burst(); | |
} | |
//delay(0); | |
} | |
//Write the remain bytes in the buffer | |
if (i > 0) { | |
myCAM.CS_HIGH(); | |
f.write(buf, i); | |
} | |
//Close the file | |
f.close(); | |
Serial.println("CAM Save Done!"); | |
} | |
////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Retrieves a stored jpg file and sends to browser based on url: http://IPaddress/1.jpg // | |
////////////////////////////////////////////////////////////////////////////////////////////////// | |
bool loadFromSpiffs(String path) { | |
String dataType = "text/plain"; | |
if (path.endsWith("/")) path += "index.htm"; | |
if (path.endsWith(".src")) path = path.substring(0, path.lastIndexOf(".")); | |
// else if (path.endsWith(".htm")) dataType = "text/html"; | |
// else if (path.endsWith(".css")) dataType = "text/css"; | |
// else if (path.endsWith(".js")) dataType = "application/javascript"; | |
// else if (path.endsWith(".png")) dataType = "image/png"; | |
// else if (path.endsWith(".gif")) dataType = "image/gif"; | |
else if (path.endsWith(".jpg")) dataType = "image/jpeg"; | |
// else if (path.endsWith(".ico")) dataType = "image/x-icon"; | |
// else if (path.endsWith(".xml")) dataType = "text/xml"; | |
// else if (path.endsWith(".pdf")) dataType = "application/pdf"; | |
// else if (path.endsWith(".zip")) dataType = "application/zip"; | |
File dataFile = SPIFFS.open(path.c_str(), "r"); | |
if (server.hasArg("download")) dataType = "application/octet-stream"; | |
if (server.streamFile(dataFile, dataType) != dataFile.size()) { | |
} | |
dataFile.close(); | |
return true; | |
} | |
///////////////////////////////////////////////////////////// | |
// sets the HTML used for the resoultions drop down // | |
///////////////////////////////////////////////////////////// | |
String getDropDown() | |
{ | |
String webString = ""; | |
webString += "<select name=\"rez\">\n"; | |
webString += " <option value=\"0\" "; | |
if (resolution == 0) | |
webString += " selected=\"seleted\" "; | |
webString += ">160x120</option>\n"; | |
webString += " <option value=\"1\" "; | |
if (resolution == 1) | |
webString += " selected=\"seleted\" "; | |
webString += ">176x144</option>\n"; | |
webString += " <option value=\"2\" "; | |
if (resolution == 2) | |
webString += " selected=\"seleted\" "; | |
webString += ">320x240</option>\n"; | |
webString += " <option value=\"3\" "; | |
if (resolution == 3) | |
webString += " selected=\"seleted\" "; | |
webString += ">352x288</option>\n"; | |
webString += " <option value=\"4\" "; | |
if (resolution == 4) | |
webString += " selected=\"seleted\" "; | |
webString += ">640x480</option>\n"; | |
webString += " <option value=\"5\" "; | |
if (resolution == 5) | |
webString += " selected=\"seleted\" "; | |
webString += ">800x600</option>\n"; | |
webString += " <option value=\"6\" "; | |
if (resolution == 6) | |
webString += " selected=\"seleted\" "; | |
webString += ">1024x768</option>\n"; | |
webString += " <option value=\"7\" "; | |
if (resolution == 7) | |
webString += " selected=\"seleted\" "; | |
webString += ">1280x1024</option>\n"; | |
webString += " <option value=\"8\" "; | |
if (resolution == 8) | |
webString += " selected=\"seleted\" "; | |
webString += ">1600x1200</option>\n"; | |
webString += " </select>\n"; | |
return webString; | |
} | |
//////////////////////////////////// | |
// capture initialization // | |
//////////////////////////////////// | |
void start_capture() { | |
myCAM.clear_fifo_flag(); | |
myCAM.start_capture(); | |
} | |
///////////////////////////////////////////// | |
// capture still image and send to client // | |
///////////////////////////////////////////// | |
void camCapture(ArduCAM myCAM) { | |
WiFiClient client = server.client(); | |
size_t len = myCAM.read_fifo_length(); | |
if (len >= 0x07ffff) { | |
Serial.println("Over size."); | |
return; | |
} else if (len == 0 ) { | |
Serial.println("Size is 0."); | |
return; | |
} | |
myCAM.CS_LOW(); | |
myCAM.set_fifo_burst(); | |
#if !(defined (ARDUCAM_SHIELD_V2) && defined (OV2640_CAM)) | |
SPI.transfer(0xFF); | |
#endif | |
if (!client.connected()) return; | |
/* | |
String response = "HTTP/1.1 200 OK\r\n"; | |
response += "Content-Type: image/jpeg\r\n"; | |
response += "Content-Length: " + String(len) + "\r\n\r\n"; | |
server.sendContent(response); | |
*/ | |
static uint8_t buffer[bufferSize] = {0xFF}; | |
while (len) { | |
size_t will_copy = (len < bufferSize) ? len : bufferSize; | |
SPI.transferBytes(&buffer[0], &buffer[0], will_copy); | |
if (!client.connected()) break; | |
client.write(&buffer[0], will_copy); | |
len -= will_copy; | |
} | |
myCAM.CS_HIGH(); | |
} | |
///////////////////////////////////////////// | |
// initiate capture and record time used // | |
///////////////////////////////////////////// | |
void serverCapture() { | |
start_capture(); | |
Serial.println("CAM Capturing"); | |
int total_time = 0; | |
total_time = millis(); | |
while (!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK)); | |
total_time = millis() - total_time; | |
Serial.print("capture total_time used (in miliseconds):"); | |
Serial.println(total_time, DEC); | |
total_time = 0; | |
Serial.println("CAM Capture Done!"); | |
total_time = millis(); | |
camCapture(myCAM); | |
total_time = millis() - total_time; | |
Serial.print("send total_time used (in miliseconds):"); | |
Serial.println(total_time, DEC); | |
Serial.println("CAM send Done!"); | |
} | |
///////////////////////////// | |
// stream video to client // | |
///////////////////////////// | |
void serverStream() { | |
WiFiClient client = server.client(); | |
String response = "HTTP/1.1 200 OK\r\n"; | |
response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n"; | |
server.sendContent(response); | |
while (client.connected()) { | |
start_capture(); | |
while (!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK)); | |
size_t len = myCAM.read_fifo_length(); | |
if (len >= 0x07ffff) { | |
Serial.println("Over size."); | |
continue; | |
} else if (len == 0 ) { | |
Serial.println("Size is 0."); | |
continue; | |
} | |
myCAM.CS_LOW(); | |
myCAM.set_fifo_burst(); | |
#if !(defined (ARDUCAM_SHIELD_V2) && defined (OV2640_CAM)) | |
SPI.transfer(0xFF); | |
#endif | |
if (!client.connected()) break; | |
response = "--frame\r\n"; | |
response += "Content-Type: image/jpeg\r\n\r\n"; | |
server.sendContent(response); | |
static uint8_t buffer[bufferSize] = {0xFF}; | |
while (len) { | |
size_t will_copy = (len < bufferSize) ? len : bufferSize; | |
SPI.transferBytes(&buffer[0], &buffer[0], will_copy); | |
if (!client.connected()) break; | |
client.write(&buffer[0], will_copy); | |
len -= will_copy; | |
} | |
myCAM.CS_HIGH(); | |
if (!client.connected()) break; | |
} | |
} | |
//////////////////////////////////////////////////////////////// | |
// Default web handler used when client access web server // | |
//////////////////////////////////////////////////////////////// | |
void handleNotFound() { | |
String ipStr = WiFi.localIP().toString(); | |
if (wifiType == 1) // AP mode = 1 | |
{ | |
ipStr = WiFi.softAPIP().toString(); | |
} | |
Serial.print("server uri: " ); Serial.println(server.uri()); | |
// if url contains request for stored image: http://IPaddress/pics/2.jpg | |
if (server.uri().indexOf(".jpg") != -1 ) | |
{ | |
loadFromSpiffs(server.uri()); | |
return; | |
} | |
//// default HTML | |
String message = "<html><head>\n"; | |
message += "</head><body style='margin:0;padding:0'>\n"; | |
message += "<div><img id=\"ArduCam\" src=\"http://" + ipStr + "/capture\" ></div>\n"; | |
message += "</body></html>\n"; | |
server.send(200, "text/html", message); | |
} | |
/////////////////////////////////////////////////////////////////////////////////// | |
// used when form is submitted and at setup to set the camera resolution // | |
/////////////////////////////////////////////////////////////////////////////////// | |
void setCamResolution(int reso) | |
{ | |
switch (reso) | |
{ | |
case 0: | |
myCAM.OV2640_set_JPEG_size(OV2640_160x120); | |
resolution = 0; | |
break; | |
case 1: | |
myCAM.OV2640_set_JPEG_size(OV2640_176x144); | |
resolution = 1; | |
break; | |
case 2: | |
myCAM.OV2640_set_JPEG_size(OV2640_320x240); | |
resolution = 2; | |
break; | |
case 3: | |
myCAM.OV2640_set_JPEG_size(OV2640_352x288); | |
resolution = 3; | |
break; | |
case 4: | |
myCAM.OV2640_set_JPEG_size(OV2640_640x480); | |
resolution = 4; | |
break; | |
case 5: | |
myCAM.OV2640_set_JPEG_size(OV2640_800x600); | |
resolution = 5; | |
break; | |
case 6: | |
myCAM.OV2640_set_JPEG_size(OV2640_1024x768); | |
resolution = 6; | |
break; | |
case 7: | |
myCAM.OV2640_set_JPEG_size(OV2640_1280x1024); | |
resolution = 7; | |
break; | |
case 8: | |
myCAM.OV2640_set_JPEG_size(OV2640_1600x1200); | |
resolution = 8; | |
break; | |
} | |
} | |
/////////////////////////////////////////////////////// | |
// deletes all files in the /pics directory // | |
/////////////////////////////////////////////////////// | |
void clearData() | |
{ | |
errMsg = "====== Data Storage Cleared ========="; | |
Dir dir = SPIFFS.openDir("/pics"); | |
while (dir.next()) { | |
SPIFFS.remove(dir.fileName()); | |
} | |
fileCount = 0; | |
fileTotalKB = 0; | |
fileUsedKB = 0; | |
handleNotFound(); | |
} | |
///////////////////////////////////// | |
// handles form submission // | |
///////////////////////////////////// | |
void handleSubmit() | |
{ | |
errMsg = ""; | |
Serial.println( "--- Form Submited ---"); | |
Serial.println( "Server args " + server.args()); | |
if (server.args() > 0 ) { | |
for ( uint8_t i = 0; i < server.args(); i++ ) { | |
// can be useful to determine the values from a form post. | |
Serial.println( "Server arg " + server.arg(i)); | |
Serial.println( "Server argName " + server.argName(i) ); | |
if (server.argName(i) == "imgMode") | |
{ | |
Serial.println(" Image Mode: " + server.arg(i)); | |
if (server.arg(i) == "stream") | |
{ | |
imgMode = 0; | |
} | |
else | |
{ | |
imgMode = 1; // capture mode | |
} | |
} | |
if (server.argName(i) == "rez") { | |
if (server.arg(i).toInt() != resolution) | |
{ | |
setCamResolution(server.arg(i).toInt()); | |
updateDataFile(); | |
//// IMPORTANT: removing or reducing the delay can result in dark images | |
delay(1500); // add a delay to allow the white balance to adjust to new resolution | |
} | |
} | |
} | |
} | |
if (imgMode == 1) // capture | |
{ | |
myCAMSaveToSPIFFS(); | |
} | |
handleNotFound(); | |
} | |
//////////////////////////////// | |
// main setup function // | |
//////////////////////////////// | |
void setup() { | |
SPIFFS.begin(); | |
delay(10); | |
// SPIFFS.format(); // uncomment to completely clear data including properties file | |
// check for properties file | |
File f = SPIFFS.open(fName, "r"); | |
if (!f) { | |
// no file exists so lets format and create a properties file | |
Serial.println("Please wait 30 secs for SPIFFS to be formatted"); | |
SPIFFS.format(); | |
Serial.println("Spiffs formatted"); | |
f = SPIFFS.open(fName, "w"); | |
if (!f) { | |
Serial.println("properties file open failed"); | |
} | |
else | |
{ | |
// write the defaults to the properties file | |
Serial.println("====== Writing to properties file ========="); | |
f.println(resolution); | |
f.close(); | |
} | |
} | |
else | |
{ | |
// if the properties file exists on startup, read it and set the defaults | |
Serial.println("Properties file exists. Reading."); | |
while (f.available()) { | |
// read line by line from the file | |
String str = f.readStringUntil('\n'); | |
Serial.println(str); | |
resolution = str.toInt(); | |
} | |
f.close(); | |
} | |
uint8_t vid, pid; | |
uint8_t temp; | |
#if defined(__SAM3X8E__) | |
Wire1.begin(); | |
#else | |
Wire.begin(); | |
#endif | |
Serial.begin(115200); | |
Serial.println("ArduCAM Start!"); | |
// set the CS as an output: | |
pinMode(CS, OUTPUT); | |
// initialize SPI: | |
SPI.begin(); | |
SPI.setFrequency(4000000); //4MHz | |
//Check if the ArduCAM SPI bus is OK | |
myCAM.write_reg(ARDUCHIP_TEST1, 0x55); | |
temp = myCAM.read_reg(ARDUCHIP_TEST1); | |
if (temp != 0x55) { | |
Serial.println("SPI1 interface Error!"); | |
} | |
//Check if the camera module type is OV2640 | |
myCAM.wrSensorReg8_8(0xff, 0x01); | |
myCAM.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid); | |
myCAM.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid); | |
if ((vid != 0x26 ) && (( pid != 0x41 ) || ( pid != 0x42 ))) | |
Serial.println("Can't find OV2640 module! pid: " + String(pid)); | |
else | |
Serial.println("OV2640 detected."); | |
//Change to JPEG capture mode and initialize the OV2640 module | |
myCAM.set_format(JPEG); | |
myCAM.InitCAM(); | |
setCamResolution(resolution); | |
myCAM.clear_fifo_flag(); | |
if (wifiType == 0) { | |
if (!strcmp(ssid, "SSID")) { | |
Serial.println("Please set your SSID"); | |
} | |
if (!strcmp(password, "PASSWORD")) { | |
Serial.println("Please set your PASSWORD"); | |
} | |
// Connect to WiFi network | |
Serial.println(); | |
Serial.println(); | |
Serial.print("Connecting to "); | |
Serial.println(ssid); | |
WiFi.mode(WIFI_STA); | |
WiFi.begin(ssid, password); | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(500); | |
Serial.print("."); | |
} | |
WiFi.config(ip, gateway, subnet); // remove this line to use DHCP | |
Serial.println("WiFi connected"); | |
Serial.println(""); | |
Serial.print("ip: "); | |
Serial.println(WiFi.localIP()); | |
} else if (wifiType == 1) { | |
Serial.println(); | |
Serial.println(); | |
Serial.print("Share AP: "); | |
Serial.println(AP_ssid); | |
Serial.print("The password is: "); | |
Serial.println(AP_password); | |
WiFi.mode(WIFI_AP); | |
WiFi.softAP(AP_ssid, AP_password); | |
Serial.println(""); | |
Serial.println("AP ip"); | |
Serial.println(WiFi.softAPIP()); | |
} | |
// setup handlers | |
server.on("/capture", HTTP_GET, serverCapture); | |
server.on("/stream", HTTP_GET, serverStream); | |
server.on("/submit", handleSubmit); | |
server.on("/clear", clearData); | |
server.onNotFound(handleNotFound); | |
server.begin(); | |
Serial.println("Server started"); | |
Dir dir = SPIFFS.openDir("/pics"); | |
while (dir.next()) { | |
fileCount++; | |
} | |
FSInfo fs_info; | |
SPIFFS.info(fs_info); | |
fileTotalKB = (int)fs_info.totalBytes; | |
fileUsedKB = (int)fs_info.usedBytes; | |
} | |
///////////////////////////// | |
// Main loop function // | |
///////////////////////////// | |
void loop() { | |
server.handleClient(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment