aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp423
1 files changed, 423 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..e15bffa
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,423 @@
+#define APP_CPU 1
+#define PRO_CPU 0
+
+#include <OV2640.h>
+#include <WebServer.h>
+#include <WiFi.h>
+#include <WiFiClient.h>
+
+#include <driver/rtc_io.h>
+#include <esp_bt.h>
+#include <esp_sleep.h>
+#include <esp_wifi.h>
+
+#define PWDN_GPIO_NUM 32
+#define RESET_GPIO_NUM -1
+#define XCLK_GPIO_NUM 0
+#define SIOD_GPIO_NUM 26
+#define SIOC_GPIO_NUM 27
+
+#define Y9_GPIO_NUM 35
+#define Y8_GPIO_NUM 34
+#define Y7_GPIO_NUM 39
+#define Y6_GPIO_NUM 36
+#define Y5_GPIO_NUM 21
+#define Y4_GPIO_NUM 19
+#define Y3_GPIO_NUM 18
+#define Y2_GPIO_NUM 5
+#define VSYNC_GPIO_NUM 25
+#define HREF_GPIO_NUM 23
+#define PCLK_GPIO_NUM 22
+
+OV2640 cam;
+
+WebServer server(80);
+
+// ===== rtos task handles =========================
+// Streaming is implemented with 3 tasks:
+TaskHandle_t tMjpeg; // handles client connections to the webserver
+TaskHandle_t tCam; // handles getting picture frames from the camera and storing
+ // them locally
+TaskHandle_t tStream; // actually streaming frames to all connected clients
+
+// frameSync semaphore is used to prevent streaming buffer as it is replaced
+// with the next frame
+SemaphoreHandle_t frameSync = NULL;
+
+// Queue stores currently connected clients to whom we are streaming
+QueueHandle_t streamingClients;
+
+// We will try to achieve 25 FPS frame rate
+const int FPS = 14;
+
+// We will handle web client requests every 50 ms (20 Hz)
+const int WSINTERVAL = 100;
+
+// Commonly used variables:
+volatile size_t camSize; // size of the current frame, byte
+volatile char *camBuf; // pointer to the current frame
+
+// ==== Memory allocator that takes advantage of PSRAM if present
+// =======================
+char *allocateMemory(char *aPtr, size_t aSize) {
+
+ // Since current buffer is too smal, free it
+ if (aPtr != NULL)
+ free(aPtr);
+
+ size_t freeHeap = ESP.getFreeHeap();
+ char *ptr = NULL;
+
+ // If memory requested is more than 2/3 of the currently free heap, try PSRAM
+ // immediately
+ if (aSize > freeHeap * 2 / 3) {
+ if (psramFound() && ESP.getFreePsram() > aSize) {
+ ptr = (char *)ps_malloc(aSize);
+ }
+ } else {
+ // Enough free heap - let's try allocating fast RAM as a buffer
+ ptr = (char *)malloc(aSize);
+
+ // If allocation on the heap failed, let's give PSRAM one more chance:
+ if (ptr == NULL && psramFound() && ESP.getFreePsram() > aSize) {
+ ptr = (char *)ps_malloc(aSize);
+ }
+ }
+
+ // Finally, if the memory pointer is NULL, we were not able to allocate any
+ // memory, and that is a terminal condition.
+ if (ptr == NULL) {
+ ESP.restart();
+ }
+ return ptr;
+}
+
+// ==== RTOS task to grab frames from the camera =========================
+void camCB(void *pvParameters) {
+
+ TickType_t xLastWakeTime;
+
+ // A running interval associated with currently desired frame rate
+ const TickType_t xFrequency = pdMS_TO_TICKS(1000 / FPS);
+
+ // Mutex for the critical section of swithing the active frames around
+ portMUX_TYPE xSemaphore = portMUX_INITIALIZER_UNLOCKED;
+
+ // Pointers to the 2 frames, their respective sizes and index of the current
+ // frame
+ char *fbs[2] = {NULL, NULL};
+ size_t fSize[2] = {0, 0};
+ int ifb = 0;
+
+ //=== loop() section ===================
+ xLastWakeTime = xTaskGetTickCount();
+
+ for (;;) {
+
+ // Grab a frame from the camera and query its size
+ cam.run();
+ size_t s = cam.getSize();
+
+ // If frame size is more that we have previously allocated - request 125%
+ // of the current frame space
+ if (s > fSize[ifb]) {
+ fSize[ifb] = s * 4 / 3;
+ fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]);
+ }
+
+ // Copy current frame into local buffer
+ char *b = (char *)cam.getfb();
+ memcpy(fbs[ifb], b, s);
+
+ // Let other tasks run and wait until the end of the current frame rate
+ // interval (if any time left)
+ taskYIELD();
+ vTaskDelayUntil(&xLastWakeTime, xFrequency);
+
+ // Only switch frames around if no frame is currently being streamed to a
+ // client Wait on a semaphore until client operation completes
+ xSemaphoreTake(frameSync, portMAX_DELAY);
+
+ // Do not allow interrupts while switching the current frame
+ portENTER_CRITICAL(&xSemaphore);
+ camBuf = fbs[ifb];
+ camSize = s;
+ ifb++;
+ ifb &= 1; // this should produce 1, 0, 1, 0, 1 ... sequence
+ portEXIT_CRITICAL(&xSemaphore);
+
+ // Let anyone waiting for a frame know that the frame is ready
+ xSemaphoreGive(frameSync);
+
+ // Technically only needed once: let the streaming task know that we have
+ // at least one frame and it could start sending frames to the clients, if
+ // any
+ xTaskNotifyGive(tStream);
+
+ // Immediately let other (streaming) tasks run
+ taskYIELD();
+
+ // If streaming task has suspended itself (no active clients to stream to)
+ // there is no need to grab frames from the camera. We can save some juice
+ // by suspedning the tasks
+ if (eTaskGetState(tStream) == eSuspended) {
+ vTaskSuspend(NULL); // passing NULL means "suspend yourself"
+ }
+ }
+}
+
+// ==== STREAMING ======================================================
+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";
+const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: ";
+const int hdrLen = strlen(HEADER);
+const int bdrLen = strlen(BOUNDARY);
+const int cntLen = strlen(CTNTTYPE);
+
+// ==== Handle connection request from clients ===============================
+void handleJPGSstream(void) {
+ // Can only acommodate 10 clients. The limit is a default for WiFi
+ // connections
+ if (!uxQueueSpacesAvailable(streamingClients))
+ return;
+
+ // Create a new WiFi Client object to keep track of this one
+ WiFiClient *client = new WiFiClient();
+ *client = server.client();
+
+ // Immediately send this client a header
+ client->write(HEADER, hdrLen);
+ client->write(BOUNDARY, bdrLen);
+
+ // Push the client to the streaming queue
+ xQueueSend(streamingClients, (void *)&client, 0);
+
+ // Wake up streaming tasks, if they were previously suspended:
+ if (eTaskGetState(tCam) == eSuspended)
+ vTaskResume(tCam);
+ if (eTaskGetState(tStream) == eSuspended)
+ vTaskResume(tStream);
+}
+
+// ==== Actually stream content to all connected clients
+// ========================
+void streamCB(void *pvParameters) {
+ char buf[16];
+ TickType_t xLastWakeTime;
+ TickType_t xFrequency;
+
+ // Wait until the first frame is captured and there is something to send
+ // to clients
+ ulTaskNotifyTake(pdTRUE, /* Clear the notification value before exiting. */
+ portMAX_DELAY); /* Block indefinitely. */
+
+ xLastWakeTime = xTaskGetTickCount();
+ for (;;) {
+ // Default assumption we are running according to the FPS
+ xFrequency = pdMS_TO_TICKS(1000 / FPS);
+
+ // Only bother to send anything if there is someone watching
+ UBaseType_t activeClients = uxQueueMessagesWaiting(streamingClients);
+ if (activeClients) {
+ // Adjust the period to the number of connected clients
+ xFrequency /= activeClients;
+
+ // Since we are sending the same frame to everyone,
+ // pop a client from the the front of the queue
+ WiFiClient *client;
+ xQueueReceive(streamingClients, (void *)&client, 0);
+
+ // Check if this client is still connected.
+
+ if (!client->connected()) {
+ // delete this client reference if s/he has disconnected
+ // and don't put it back on the queue anymore. Bye!
+ delete client;
+ } else {
+
+ // Ok. This is an actively connected client.
+ // Let's grab a semaphore to prevent frame changes while we
+ // are serving this frame
+ xSemaphoreTake(frameSync, portMAX_DELAY);
+
+ client->write(CTNTTYPE, cntLen);
+ sprintf(buf, "%d\r\n\r\n", camSize);
+ client->write(buf, strlen(buf));
+ client->write((char *)camBuf, (size_t)camSize);
+ client->write(BOUNDARY, bdrLen);
+
+ // Since this client is still connected, push it to the end
+ // of the queue for further processing
+ xQueueSend(streamingClients, (void *)&client, 0);
+
+ // The frame has been served. Release the semaphore and let other tasks
+ // run. If there is a frame switch ready, it will happen now in between
+ // frames
+ xSemaphoreGive(frameSync);
+ taskYIELD();
+ }
+ } else {
+ // Since there are no connected clients, there is no reason to waste
+ // battery running
+ vTaskSuspend(NULL);
+ }
+ // Let other tasks run after serving every client
+ taskYIELD();
+ vTaskDelayUntil(&xLastWakeTime, xFrequency);
+ }
+}
+
+const char JHEADER[] = "HTTP/1.1 200 OK\r\n"
+ "Content-disposition: inline; filename=capture.jpg\r\n"
+ "Content-type: image/jpeg\r\n\r\n";
+const int jhdLen = strlen(JHEADER);
+
+// ==== Serve up one JPEG frame =============================================
+void handleJPG(void) {
+ WiFiClient client = server.client();
+
+ if (!client.connected())
+ return;
+ cam.run();
+ client.write(JHEADER, jhdLen);
+ client.write((char *)cam.getfb(), cam.getSize());
+}
+
+// ==== Handle invalid URL requests ============================================
+void handleNotFound() {
+ String message = "Server is running!\n\n";
+ message += "URI: ";
+ message += server.uri();
+ message += "\nMethod: ";
+ message += (server.method() == HTTP_GET) ? "GET" : "POST";
+ message += "\nArguments: ";
+ message += server.args();
+ message += "\n";
+ server.send(200, "text / plain", message);
+}
+
+// ======== Server Connection Handler Task ==========================
+void mjpegCB(void *pvParameters) {
+ TickType_t xLastWakeTime;
+ const TickType_t xFrequency = pdMS_TO_TICKS(WSINTERVAL);
+
+ // Creating frame synchronization semaphore and initializing it
+ frameSync = xSemaphoreCreateBinary();
+ xSemaphoreGive(frameSync);
+
+ // Creating a queue to track all connected clients
+ streamingClients = xQueueCreate(10, sizeof(WiFiClient *));
+
+ //=== setup section ==================
+
+ // Creating RTOS task for grabbing frames from the camera
+ xTaskCreatePinnedToCore(camCB, // callback
+ "cam", // name
+ 4096, // stacj size
+ NULL, // parameters
+ 2, // priority
+ &tCam, // RTOS task handle
+ APP_CPU); // core
+
+ // Creating task to push the stream to all connected clients
+ xTaskCreatePinnedToCore(streamCB, "strmCB", 4 * 1024,
+ NULL, //(void*) handler,
+ 2, &tStream, APP_CPU);
+
+ // Registering webserver handling routines
+ server.on("/mjpeg/1", HTTP_GET, handleJPGSstream);
+ server.on("/jpg", HTTP_GET, handleJPG);
+ server.onNotFound(handleNotFound);
+
+ // Starting webserver
+ server.begin();
+
+ //=== loop() section ===================
+ xLastWakeTime = xTaskGetTickCount();
+ for (;;) {
+ server.handleClient();
+
+ // After every server client handling request, we let other tasks run and
+ // then pause
+ taskYIELD();
+ vTaskDelayUntil(&xLastWakeTime, xFrequency);
+ }
+}
+
+// ==== SETUP method
+// ==================================================================
+void setup() {
+
+ // Setup Serial connection:
+ Serial.begin(115200);
+ delay(1000); // wait for a second to let Serial connect
+
+ // Configure the camera
+ 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;
+ config.xclk_freq_hz = 20000000;
+ config.pixel_format = PIXFORMAT_JPEG;
+
+ // Frame parameters: pick one
+ // config.frame_size = FRAMESIZE_UXGA;
+ // config.frame_size = FRAMESIZE_SVGA;
+ // config.frame_size = FRAMESIZE_QVGA;
+ config.frame_size = FRAMESIZE_VGA;
+ config.jpeg_quality = 12;
+ config.fb_count = 2;
+
+#if defined(CAMERA_MODEL_ESP_EYE)
+ pinMode(13, INPUT_PULLUP);
+ pinMode(14, INPUT_PULLUP);
+#endif
+
+ if (cam.init(config) != ESP_OK) {
+ Serial.println("Error initializing the camera");
+ delay(10000);
+ ESP.restart();
+ }
+
+ // Configure and connect to WiFi
+ IPAddress ip;
+
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(WIFI_SSID, WIFI_PASS);
+
+ Serial.print("Connecting to WiFi");
+ while (WiFi.status() != WL_CONNECTED) {
+ delay(500);
+ Serial.print(F("."));
+ }
+ ip = WiFi.localIP();
+ Serial.println(F("WiFi connected"));
+ Serial.println("");
+ Serial.print("Stream Link: http://");
+ Serial.print(ip);
+ Serial.println("/mjpeg/1");
+
+ // Start mainstreaming RTOS task
+ xTaskCreatePinnedToCore(mjpegCB, "mjpeg", 4 * 1024, NULL, 2, &tMjpeg,
+ APP_CPU);
+}
+
+void loop() { vTaskDelay(1000); }