Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM
- and ESP-EYE modules.
- This is tested to work with VLC and Blynk video widget and can support up to 10
- simultaneously connected streaming clients.
- Simultaneous streaming is implemented with dedicated FreeRTOS tasks.
- Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board
- (https://www.instructables.com/id/9-RTSP-Video-Streamer-Using-the-ESP32-CAM-Board/)
- Board: AI-Thinker ESP32-CAM or ESP-EYE
- Compile as:
- ESP32 Dev Module
- CPU Freq: 240
- Flash Freq: 80
- Flash mode: QIO
- Flash Size: 4Mb
- Partrition: Minimal SPIFFS
- PSRAM: Enabled
- */
- // ESP32 has two cores: APPlication core and PROcess core (the one that runs ESP32 SDK stack)
- #define APP_CPU 1
- #define PRO_CPU 0
- #include "esp_camera.h"
- #include "ov2640.h"
- #include <WiFi.h>
- #include <WebServer.h>
- #include <WiFiClient.h>
- #include <EEPROM.h>
- #include <StringSplitter.h>
- #include <esp_bt.h>
- #include <esp_wifi.h>
- #include <esp_sleep.h>
- #include <driver/rtc_io.h>
- #include <Adafruit_INA219.h>
- #include <Wire.h>
- #include <driver/adc.h>
- #include "driver/rtc_io.h"
- #include "driver/gpio.h"
- #include <esp_adc_cal.h>
- #include <HTTPClient.h>
- #include "soc/soc.h"
- #include "soc/rtc_cntl_reg.h"
- #define CAMERA_MODEL_AI_THINKER
- #define MAX_CLIENTS 10
- #include "camera_pins.h"
- /*
- Next one is an include with wifi credentials.
- This is what you need to do:
- 1. Create a file called "home_wifi_multi.h" in the same folder OR under a separate subfolder of the "libraries" folder of Arduino IDE. (You are creating a "fake" library really - I called it "MySettings").
- 2. Place the following text in the file:
- #define SSID1 "replace with your wifi ssid"
- #define PWD1 "replace your wifi password"
- 3. Save.
- Should work then
- */
- bool disable_brownout_detector = true;
- uint8_t dis_count = 0;
- bool sleep_status = false;
- bool client_connected = true;
- uint8_t sleep_cooldown_base = 20;
- uint8_t sleep_cooldown = sleep_cooldown_base;
- bool LED_control_active = false;
- #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
- #define TIME_TO_SLEEP 10 /* Time ESP32 will go to sleep (in seconds) */
- #define I2C_SDA 14
- #define I2C_SCL 15
- TwoWire I2CSensors = TwoWire(0);
- Adafruit_INA219 ina219_bat(0x40);
- Adafruit_INA219 ina219_sol(0x41);
- char ssid_char[100];
- char pass_char[100];
- #define LED_BUILTIN 4
- void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info){
- Serial.println("Connected to AP successfully!");
- }
- void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info){
- Serial.println("WiFi connected");
- Serial.println("IP address: ");
- Serial.println(WiFi.localIP());
- }
- void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info){
- Serial.println("Disconnected from WiFi access point");
- Serial.print("WiFi lost connection. Reason: ");
- Serial.println(info.disconnected.reason);
- }
- float p_current = 0;
- int measurements = 0;
- int active_sec = 0;
- float bat_shuntvoltage = 0;
- float bat_busvoltage = 0;
- float bat_current_mA = 0;
- float bat_loadvoltage = 0;
- float bat_power_mW = 0;
- float sol_shuntvoltage = 0;
- float sol_busvoltage = 0;
- float sol_current_mA = 0;
- float sol_loadvoltage = 0;
- float sol_power_mW = 0;
- float free_memory = 0;
- int wifi_signal = 0;
- int batteryPercent = 0;
- String WiFi_mode = "AP";
- String memMODE = "";
- String memSSID = "";
- String memPASS = "";
- String memIP = "";
- bool rebooting = false;
- String msg = "";
- void writeEEPROM(int addrOffset, const String &strToWrite)
- {
- byte len = strToWrite.length();
- Serial.println("Writing to EEPROM:");
- EEPROM.write(addrOffset, len);
- for (int i = 0; i < len; i++)
- {
- EEPROM.write(addrOffset + 1 + i, strToWrite[i]);
- Serial.print(strToWrite[i]);
- }
- }
- String readEEPROM(int addrOffset)
- {
- int newStrLen = EEPROM.read(addrOffset);
- char data[newStrLen + 1];
- Serial.println("Reading from EEPROM:");
- for (int i = 0; i < newStrLen; i++)
- {
- data[i] = EEPROM.read(addrOffset + 1 + i);
- Serial.print(data[i]);
- }
- data[newStrLen] = '\0';
- return String(data);
- }
- WebServer server(80);
- void handleStatus()
- {
- sleep_cooldown = sleep_cooldown_base;
- client_connected = true;
- if (sleep_status == true)
- {
- wakeModemSleep();
- }
- server.sendHeader("Connection", "keep-alive");
- server.sendHeader("Keep-Alive", "timeout=2000");
- server.sendHeader("Access-Control-Allow-Origin", "*");
- server.send(200, "text/json", getStatus());
- }
- void handleRoot()
- {
- sleep_cooldown = sleep_cooldown_base;
- client_connected = true;
- if (sleep_status == true)
- {
- wakeModemSleep();
- }
- String myVariable = "@";
- String JS = "document.addEventListener(\'DOMContentLoaded\', function (event) {\n var baseHost = document.location.origin;\n var streamUrl = baseHost + \'/stream\';\n \n\n const hide = el => {\n el.classList.add(\'hidden\')\n }\n const show = el => {\n el.classList.remove(\'hidden\')\n }\n\n const disable = el => {\n el.classList.add(\'disabled\')\n el.disabled = true\n }\n\n const enable = el => {\n el.classList.remove(\'disabled\')\n el.disabled = false\n }\n\n const updateValue = (el, value, updateRemote) => {\n updateRemote = updateRemote == null ? true : updateRemote\n let initialValue\n if (el.type === \'checkbox\') {\n initialValue = el.checked\n value = !!value\n el.checked = value\n } else {\n initialValue = el.value\n el.value = value\n }\n\n if (updateRemote \&\& initialValue !== value) {\n updateConfig(el);\n } else if(!updateRemote){\n\n }\n }\n\n function updateConfig (el) {\n let value\n switch (el.type) {\n case \'checkbox\':\n value = el.checked ? 1 : 0\n break\n case \'range\':\n case \'select-one\':\n value = el.value\n break\n case \'button\':\n case \'submit\':\n value = \'1\'\n break\n default:\n return\n }\n\n const query = `${baseHost}/control?var=${el.id}\&val=${value}`\n\n fetch(query)\n .then(response => {\n console.log(`request to ${query} finished, status: ${response.status}`)\n })\n }\n\n document\n .querySelectorAll(\'.close\')\n .forEach(el => {\n el.onclick = () => {\n hide(el.parentNode)\n }\n })\n\n\n fetch(`${baseHost}/control_status`)\n .then(function (response) {\n return response.json()\n })\n .then(function (state) {\n document\n .querySelectorAll(\'.default-action\')\n .forEach(el => {\n updateValue(el, state[el.id], false)\n })\n })\n\n const view = document.getElementById(\'stream\')\n const viewContainer = document.getElementById(\'stream-container\')\n view.src = `${streamUrl}`\n\n\n\n document\n .querySelectorAll(\'.default-action\')\n .forEach(el => {\n el.onchange = () => updateConfig(el)\n })\n\n\n})\nsetInterval(function() {\n\tvar baseHost = document.location.origin;\n\tfetch(`${baseHost + \'/upd\'}`)\t\t\n\t\t.then(x => x.json())\n\t\t.then(obj => {\n\t\t\tdocument.getElementById(\"bat_percent\").innerHTML = obj.batteryPercent + \"%\";\t\n\t\t\tdocument.getElementById(\"bat_voltage\").innerHTML = obj.batteryVoltage + \"V\";\n\t\t\tdocument.getElementById(\"bat_current\").innerHTML = obj.batteryCurrent + \"mA\";\n\t\t\tdocument.getElementById(\"wifi_signal\").innerHTML = obj.wifiSignal + \"%\";\n\t\t\tdocument.getElementById(\"sol_voltage\").innerHTML = obj.SolarVoltage + \"V\";\n\t\t\tdocument.getElementById(\"sol_current\").innerHTML = obj.SolarCurrent + \"mA\";\n\t\t\tdocument.getElementById(\"free_mem\").innerHTML = obj.memory + \"kB\";\n\t\t\tdocument.getElementById(\"active_time\").innerHTML = obj.active_time + \"s\";\n\t\t});\n\n\n}, 5000);";
- String html ="<!doctype html> <html> <head> <meta charset=\"utf-8\"> <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"> <title>ESP32_CAM</title> <style>body{ font-family:Arial,Helvetica,sans-serif; background:#181818; color:#efefef; font-size:16px } h2{ font-size:18px } section.main{ display:flex } #menu,section.main{ flex-direction:column } #menu{ display:none; flex-wrap:nowrap; min-width:170px; background:#363636; padding:8px; border-radius:4px; margin-top:-10px; margin-right:10px } #content{ display:flex; flex-direction: column; align-items: stretch; justify-content: center; } figure{ padding:0; margin:0; -webkit-margin-before:0; margin-block-start:0; -webkit-margin-after:0; margin-block-end:0; -webkit-margin-start:0; margin-inline-start:0; -webkit-margin-end:0; margin-inline-end:0 } figure img{ display:block; width:100%; height:auto; border-radius:4px; margin-top:8px } ";
- html += myVariable;
- html +="media (min-width:800px) and (orientation:landscape){ #content{ display:flex; flex-wrap:nowrap; flex-direction: column; align-items: center; justify-content: center; } figure img{ display:block; max-width:100%; max-height:calc(100vh - 40px); width:auto; height:auto } figure{ padding:0; margin:0; -webkit-margin-before:0; margin-block-start:0; -webkit-margin-after:0; margin-block-end:0; -webkit-margin-start:0; margin-inline-start:0; -webkit-margin-end:0; margin-inline-end:0 } } section#buttons{ display:flex; flex-wrap:nowrap; justify-content:space-between } #nav-toggle{ cursor:pointer; display:block } #nav-toggle-cb{ outline:0; opacity:0; width:0; height:0 } #nav-toggle-cb:checked+#menu{ display:flex } .section_content{ display:flex; flex-direction:column; flex-wrap:nowrap; justify-content:space-between; } .stats{ display:flex; flex-wrap:nowrap; justify-content:space-between; font-size:14px; padding:5px 0px; } .input-group{ display:flex; flex-wrap:nowrap; line-height:22px; margin:5px 0 } .input-group>label{ display:inline-block; padding-right:10px; min-width:47% } .input-group input,.input-group select{ flex-grow:1 } .range-max,.range-min{ display:inline-block; padding:0 5px } button{ display:block; margin:5px; padding:0 12px; border:0; line-height:28px; cursor:pointer; color:#fff; background:#ff3034; border-radius:5px; font-size:16px; outline:0 } button:hover{ background:#ff494d } button:active{ background:#f21c21 } button.disabled{ cursor:default; background:#a0a0a0 } input[type=range]{ -webkit-appearance:none; width:100%; height:22px; background:#363636; cursor:pointer; margin:0 } input[type=range]:focus{ outline:0 } input[type=range]::-webkit-slider-runnable-track{ width:100%; height:2px; cursor:pointer; background:#efefef; border-radius:0; border:0 solid #efefef } input[type=range]::-webkit-slider-thumb{ border:1px solid rgba(0,0,30,0); height:22px; width:22px; border-radius:50px; background:#ff3034; cursor:pointer; -webkit-appearance:none; margin-top:-11.5px } input[type=range]:focus::-webkit-slider-runnable-track{ background:#efefef } input[type=range]::-moz-range-track{ width:100%; height:2px; cursor:pointer; background:#efefef; border-radius:0; border:0 solid #efefef } input[type=range]::-moz-range-thumb{ border:1px solid rgba(0,0,30,0); height:22px; width:22px; border-radius:50px; background:#ff3034; cursor:pointer } input[type=range]::-ms-track{ width:100%; height:2px; cursor:pointer; background:0 0; border-color:transparent; color:transparent } input[type=range]::-ms-fill-lower{ background:#efefef; border:0 solid #efefef; border-radius:0 } input[type=range]::-ms-fill-upper{ background:#efefef; border:0 solid #efefef; border-radius:0 } input[type=range]::-ms-thumb{ border:1px solid rgba(0,0,30,0); height:22px; width:22px; border-radius:50px; background:#ff3034; cursor:pointer; height:2px } input[type=range]:focus::-ms-fill-lower{ background:#efefef } input[type=range]:focus::-ms-fill-upper{ background:#363636 } .switch{ display:block; position:relative; line-height:22px; font-size:16px; height:22px } .switch input{ outline:0; opacity:0; width:0; height:0 } .slider{ width:50px; height:22px; border-radius:22px; cursor:pointer; background-color:grey } .slider,.slider:before{ display:inline-block; transition:.4s } .slider:before{ position:relative; content:\"\"; border-radius:50%; height:16px; width:16px; left:4px; top:3px; background-color:#fff } input:checked+.slider{ background-color:#ff3034 } input:checked+.slider:before{ -webkit-transform:translateX(26px); transform:translateX(26px) } select{ border:1px solid #363636; font-size:14px; height:22px; outline:0; border-radius:5px } .image-container{ position:relative; width: 100%; min-width:80px; min-height:60px; height: auto; } .close{ position:absolute; right:5px; top:5px; background:#ff3034; width:16px; height:16px; border-radius:100px; color:#fff; text-align:center; line-height:18px; cursor:pointer } .hidden{ display:none } </style> </head> <body> <section class=\"main\"> <div id=\"content\" > <figure> <div id=\"stream-container\" class=\"image-container\"> <div class=\"close\" id=\"close-stream\">W</div> <img id=\"stream\" src=\"\"> </div> </figure> <div id=\"sidebar\"> <input type=\"checkbox\" id=\"nav-toggle-cb\" checked=\"checked\"> <nav id=\"menu\"> <div class=\"input-group\" id=\"flash-group\"> <label for=\"flash\">Flash</label> <div class=\"switch\"> <input id=\"flash\" type=\"checkbox\" class=\"default-action\"> <label class=\"slider\" for=\"flash\"></label> </div> </div> <br> <div class=\"section_content\"> <div class=\"stats\"> <text>Battery:</text> <text id=\"bat_percent\">100%</text> </div> <div class=\"stats\"> <text>WiFi:</text> <text id=\"wifi_signal\">100%</text> </div> <div class=\"stats\"> <text>Bat. Voltage:</text> <text id=\"bat_voltage\">0 V</text> </div> <div class=\"stats\"> <text>Bat. Current:</text> <text id=\"bat_current\">0 mA</text> </div> <div class=\"stats\"> <text>Solar Voltage:</text> <text id=\"sol_voltage\">0 V</text> </div> <div class=\"stats\"> <text>Solar Current:</text> <text id=\"sol_current\">0 mA</text> </div> <div class=\"stats\"> <text>Free Memory:</text> <text id=\"free_mem\">0 kB</text> </div> <div class=\"stats\"> <text>Active Time:</text> <text id=\"active_time\">0 s</text> </div> </div> </nav> </div> </div> </section> <script> ";
- html += JS;
- html +=" </script> </body> </html>";
- server.send(200, "text/html", html);
- }
- void relayControl()
- {
- if(server.arg("var") == "flash")
- {
- if(server.arg("val") == "1")
- {
- LED_control_active = true;
- digitalWrite(4, HIGH);
- }
- else
- {
- LED_control_active = false;
- digitalWrite(LED_BUILTIN, LOW);
- }
- }
- }
- void handleRestart()
- {
- Serial.println("Managed restart");
- delay(1000);
- ESP.restart();
- }
- void sendSettings() {
- String ssid = "";
- String pass = "";
- String ip = "";
- int ap = 0;
- int sta = 0;
- String mode = "AP";
- if (server.hasArg("ssid"))
- {
- ssid = String(server.arg("ssid"));
- }
- if (server.hasArg("pass"))
- {
- pass = String(server.arg("pass"));
- }
- if (server.hasArg("ip"))
- {
- json = String(server.arg("ip"));
- }
- if (server.hasArg("ap_box"))
- {
- ap = 1;
- }
- if (server.hasArg("sta_box"))
- {
- sta = 1;
- }
- if (ap == 1 && sta == 1) mode = "APSTA";
- if (ap == 1 && sta == 0) mode = "AP";
- if (ap == 0 && sta == 1) mode = "STA";
- String memString = mode+","+ssid+","+pass+","+ip;
- rebooting = true;
- handleRoot();
- writeEEPROM(0, memString);
- EEPROM.commit();
- delay(4000);
- ESP.restart();
- }
- void parseMemory(String memString)
- {
- if (memString.length() > 0)
- {
- StringSplitter *splitter = new StringSplitter(memString, ',', 4);
- memMODE = splitter->getItemAtIndex(0);
- memSSID = splitter->getItemAtIndex(1);
- memPASS = splitter->getItemAtIndex(2);
- memIP = splitter->getItemAtIndex(3);
- }
- }
- String parseMAC(String macString)
- {
- if (macString.length() > 0)
- {
- StringSplitter *splitter = new StringSplitter(macString, ':', 4);
- String S0 = splitter->getItemAtIndex(0);
- String S1 = splitter->getItemAtIndex(1);
- String S2 = splitter->getItemAtIndex(2);
- String S3 = splitter->getItemAtIndex(3);
- return S0+S1+S2;
- }
- }
- void handleSettings()
- {
- sleep_cooldown = sleep_cooldown_base;
- client_connected = true;
- if (sleep_status == true)
- {
- wakeModemSleep();
- }
- String JS = "setInterval(function() {\n\tvar baseHost = document.location.origin;\n\tfetch(`${baseHost + \'/upd\'}`);\n\n\n}, 5000);";
- String html ="<!DOCTYPE html> <html> <head> <title>ESP8266 Multimeter Settings</title> <meta charset=\"UTF-8\" name=\"viewport\" content=\"width=device-width, initial-scale=1\"> </head> <body> <h1>ESP8266 Multimeter Settings</h1> <form action=\"/sendSettings\"> <label for=\"ssid\">Network name: </label> <input type=\"text\" id=\"ssid\" name=\"ssid\" value=\"";
- html += memSSID;
- html +="\"><br><br> <label for=\"pass\">Network password: </label> <input type=\"text\" id=\"pass\" name=\"pass\" value=\"";
- html += memPASS;
- html +="\"><br><br> <label for=\"ip\">Desired Static IP: </label> <input type=\"text\" id=\"ip\" name=\"ip\" value=\"";
- html += memIP;
- html +="\"><br><br> <input type=\"checkbox\" id=\"ap_box\" name=\"ap_box\" value=\"true\" ";
- if (WiFi_mode == "AP" || WiFi_mode == "APSTA") html += " checked";
- html +="> <label for=\"ap_box\"> Access Point Mode</label><br><br> <input type=\"checkbox\" id=\"sta_box\" name=\"sta_box\" value=\"true\" ";
- if (WiFi_mode == "STA" || WiFi_mode == "APSTA") html += " checked";
- html +="> <label for=\"sta_box\"> Station Mode</label><br><br>";
- if (!rebooting) html += " <input type=\"submit\" value=\"Submit and Reboot\"> ";
- html +="<p id=\"info\">";
- html += msg;
- html +="</p> </form> <script> ";
- html += JS;
- html +=" </script> </body> </html>";
- server.send(200, "text/html", html);
- }
- // ===== 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
- uint8_t noActiveClients; // number of active clients
- // frameSync semaphore is used to prevent streaming buffer as it is replaced with the next frame
- SemaphoreHandle_t frameSync = NULL;
- // We will try to achieve 24 FPS frame rate
- const int FPS = 24;
- // We will handle web client requests every 100 ms (10 Hz)
- const int WSINTERVAL = 100;
- // ======== 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 );
- //=== setup section ==================
- // Creating RTOS task for grabbing frames from the camera
- xTaskCreatePinnedToCore(
- camCB, // callback
- "cam", // name
- 4 * 1024, // stacj size
- NULL, // parameters
- 2, // priority
- &tCam, // RTOS task handle
- PRO_CPU); // core
- // Registering webserver handling routines
- server.on("/stream", HTTP_GET, handleJPGSstream);
- server.on("/jpg", HTTP_GET, handleJPG);
- server.on("/upd", HTTP_GET, handleStatus);
- server.on("/control", HTTP_GET, relayControl);
- server.on("/restart", HTTP_GET, handleRestart);
- server.on("/settings", HTTP_GET, handleSettings);
- server.on("/sendSettings", HTTP_GET, sendSettings);
- server.on("/", HTTP_GET, handleRoot);
- server.onNotFound(handleNotFound);
- // Starting webserver
- server.begin();
- noActiveClients = 0;
- Serial.printf("\nmjpegCB: free heap (start) : %d\n", ESP.getFreeHeap());
- //=== 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);
- }
- }
- // Current frame information
- volatile uint32_t frameNumber;
- volatile size_t camSize; // size of the current frame, byte
- volatile char* camBuf; // pointer to the current frame
- // ==== 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);
- // 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;
- frameNumber = 0;
- //=== loop() section ===================
- xLastWakeTime = xTaskGetTickCount();
- for (;;) {
- // Grab a frame from the camera and query its size
- camera_fb_t* fb = NULL;
- fb = esp_camera_fb_get();
- size_t s = fb->len;
- // If frame size is more that we have previously allocated - request 125% of the current frame space
- if (s > fSize[ifb]) {
- fSize[ifb] = s + s;
- fbs[ifb] = allocateMemory(fbs[ifb], fSize[ifb]);
- }
- // Copy current frame into local buffer
- char* b = (char *)fb->buf;
- memcpy(fbs[ifb], b, s);
- esp_camera_fb_return(fb);
- // 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 frame copying while switching the current frame
- xSemaphoreTake( frameSync, xFrequency );
- camBuf = fbs[ifb];
- camSize = s;
- ifb++;
- ifb &= 1; // this should produce 1, 0, 1, 0, 1 ... sequence
- frameNumber++;
- // Let anyone waiting for a frame know that the frame is ready
- xSemaphoreGive( frameSync );
- // 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 ( noActiveClients == 0 ) {
- Serial.printf("mjpegCB: free heap : %d\n", ESP.getFreeHeap());
- Serial.printf("mjpegCB: min free heap) : %d\n", ESP.getMinFreeHeap());
- Serial.printf("mjpegCB: max alloc free heap : %d\n", ESP.getMaxAllocHeap());
- Serial.printf("mjpegCB: tCam stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(tCam));
- Serial.flush();
- vTaskSuspend(NULL); // passing NULL means "suspend yourself"
- }
- }
- }
- // ==== 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);
- char* ptr = NULL;
- ptr = (char*) ps_malloc(aSize);
- // If the memory pointer is NULL, we were not able to allocate any memory, and that is a terminal condition.
- if (ptr == NULL) {
- Serial.println("Out of memory!");
- delay(5000);
- ESP.restart();
- }
- return ptr;
- }
- // ==== 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);
- struct streamInfo {
- uint32_t frame;
- WiFiClient client;
- TaskHandle_t task;
- char* buffer;
- size_t len;
- };
- // ==== Handle connection request from clients ===============================
- void handleJPGSstream(void)
- {
- if ( noActiveClients >= MAX_CLIENTS ) return;
- Serial.printf("handleJPGSstream start: free heap : %d\n", ESP.getFreeHeap());
- streamInfo* info = new streamInfo;
- info->frame = frameNumber - 1;
- info->client = server.client();
- info->buffer = NULL;
- info->len = 0;
- // Creating task to push the stream to all connected clients
- int rc = xTaskCreatePinnedToCore(
- streamCB,
- "strmCB",
- 3 * 1024,
- (void*) info,
- 2,
- &info->task,
- APP_CPU);
- if ( rc != pdPASS ) {
- Serial.printf("handleJPGSstream: error creating RTOS task. rc = %d\n", rc);
- Serial.printf("handleJPGSstream: free heap : %d\n", ESP.getFreeHeap());
- // Serial.printf("stk high wm: %d\n", uxTaskGetStackHighWaterMark(tSend));
- delete info;
- }
- noActiveClients++;
- // Wake up streaming tasks, if they were previously suspended:
- if ( eTaskGetState( tCam ) == eSuspended ) vTaskResume( tCam );
- }
- // ==== Actually stream content to all connected clients ========================
- void streamCB(void * pvParameters) {
- char buf[16];
- TickType_t xLastWakeTime;
- TickType_t xFrequency;
- streamInfo* info = (streamInfo*) pvParameters;
- if ( info == NULL ) {
- Serial.println("streamCB: a NULL pointer passed");
- }
- // Immediately send this client a header
- info->client.write(HEADER, hdrLen);
- info->client.write(BOUNDARY, bdrLen);
- taskYIELD();
- xLastWakeTime = xTaskGetTickCount();
- xFrequency = pdMS_TO_TICKS(1000 / FPS);
- for (;;) {
- // Only bother to send anything if there is someone watching
- if ( info->client.connected() ) {
- sleep_cooldown = sleep_cooldown_base;
- client_connected = true;
- if (sleep_status == true)
- {
- wakeModemSleep();
- }
- if ( info->frame != frameNumber) {
- xSemaphoreTake( frameSync, portMAX_DELAY );
- if ( info->buffer == NULL ) {
- info->buffer = allocateMemory (info->buffer, camSize);
- info->len = camSize;
- }
- else {
- if ( camSize > info->len ) {
- info->buffer = allocateMemory (info->buffer, camSize);
- info->len = camSize;
- }
- }
- memcpy(info->buffer, (const void*) camBuf, info->len);
- xSemaphoreGive( frameSync );
- taskYIELD();
- info->frame = frameNumber;
- info->client.write(CTNTTYPE, cntLen);
- sprintf(buf, "%d\r\n\r\n", info->len);
- info->client.write(buf, strlen(buf));
- info->client.write((char*) info->buffer, (size_t)info->len);
- info->client.write(BOUNDARY, bdrLen);
- info->client.flush();
- }
- }
- else {
- // client disconnected - clean up.
- noActiveClients--;
- Serial.printf("streamCB: Stream Task stack wtrmark : %d\n", uxTaskGetStackHighWaterMark(info->task));
- Serial.flush();
- info->client.flush();
- info->client.stop();
- if ( info->buffer ) {
- free( info->buffer );
- info->buffer = NULL;
- }
- delete info;
- info = NULL;
- vTaskDelete(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;
- camera_fb_t* fb = esp_camera_fb_get();
- client.write(JHEADER, jhdLen);
- client.write((char*)fb->buf, fb->len);
- esp_camera_fb_return(fb);
- }
- // ==== 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);
- }
- // ==== SETUP method ==================================================================
- void setup()
- {
- // Setup Serial connection:
- EEPROM.begin(512);
- Serial.begin(115200);
- delay(1000); // wait for a second to let Serial connect
- Serial.printf("setup: free heap : %d\n", ESP.getFreeHeap());
- if (disable_brownout_detector) WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
- #define CUSTOM_PIN 33
- pinMode(CUSTOM_PIN, OUTPUT);
- digitalWrite(CUSTOM_PIN, HIGH);
- delay(100);
- I2CSensors.begin(I2C_SDA, I2C_SCL, 100000);
- ina219_bat.begin(&I2CSensors);
- ina219_sol.begin(&I2CSensors);
- static camera_config_t camera_config = {
- .pin_pwdn = PWDN_GPIO_NUM,
- .pin_reset = RESET_GPIO_NUM,
- .pin_xclk = XCLK_GPIO_NUM,
- .pin_sscb_sda = SIOD_GPIO_NUM,
- .pin_sscb_scl = SIOC_GPIO_NUM,
- .pin_d7 = Y9_GPIO_NUM,
- .pin_d6 = Y8_GPIO_NUM,
- .pin_d5 = Y7_GPIO_NUM,
- .pin_d4 = Y6_GPIO_NUM,
- .pin_d3 = Y5_GPIO_NUM,
- .pin_d2 = Y4_GPIO_NUM,
- .pin_d1 = Y3_GPIO_NUM,
- .pin_d0 = Y2_GPIO_NUM,
- .pin_vsync = VSYNC_GPIO_NUM,
- .pin_href = HREF_GPIO_NUM,
- .pin_pclk = PCLK_GPIO_NUM,
- .xclk_freq_hz = 20000000,
- .ledc_timer = LEDC_TIMER_0,
- .ledc_channel = LEDC_CHANNEL_0,
- .pixel_format = PIXFORMAT_JPEG,
- /*
- FRAMESIZE_96X96, // 96x96
- FRAMESIZE_QQVGA, // 160x120
- FRAMESIZE_QCIF, // 176x144
- FRAMESIZE_HQVGA, // 240x176
- FRAMESIZE_240X240, // 240x240
- FRAMESIZE_QVGA, // 320x240
- FRAMESIZE_CIF, // 400x296
- FRAMESIZE_HVGA, // 480x320
- FRAMESIZE_VGA, // 640x480
- FRAMESIZE_SVGA, // 800x600
- FRAMESIZE_XGA, // 1024x768
- FRAMESIZE_HD, // 1280x720
- FRAMESIZE_SXGA, // 1280x1024
- FRAMESIZE_UXGA, // 1600x1200
- */
- .frame_size = FRAMESIZE_XGA,
- .jpeg_quality = 10,
- .fb_count = 1
- };
- if (esp_camera_init(&camera_config) != ESP_OK) {
- Serial.println("Error initializing the camera");
- delay(1000);
- ESP.restart();
- }
- sensor_t* s = esp_camera_sensor_get();
- s->set_vflip(s, 0);
- s->set_hmirror(s, 0);
- pinMode(LED_BUILTIN, OUTPUT);
- btStop();
- Serial.println("");
- Serial.println("Bluetooth disabled...");
- // Configure and connect to WiFi
- String memoryStr = readEEPROM(0);
- parseMemory(memoryStr);
- Serial.println("memMODE = " + memMODE);
- if (memMODE != "AP" && memMODE != "STA" && memMODE != "APSTA") memMODE = "";
- if (memMODE == "AP" || memMODE.length() <= 0)
- {
- if (memMODE.length() <= 0) Serial.println("Reading Memory - MEMORY IS EMPTY, Setting to ACCESS POINT mode");
- else Serial.println("Reading Memory - Memory found, Setting to ACCESS POINT mode");
- IPAddress localIp(192,168,1,1);
- IPAddress gateway(192,168,1,1);
- IPAddress subnet(255,255,255,0);
- WiFi.softAPConfig(localIp, gateway, subnet);
- String macString = parseMAC(WiFi.macAddress());
- macString = "ESP_MM_"+macString;
- macString.toCharArray(ssid_char, macString.length()+1);
- WiFi.softAP(ssid_char);
- Serial.println("WiFi AP set! " + macString);
- WiFi_mode = "AP";
- }
- else if (memMODE == "APSTA")
- {
- Serial.println("Reading Memory - Memory found, Setting to STATION + ACCESS POINT mode");
- IPAddress localIp(192,168,1,1);
- IPAddress gateway(192,168,1,1);
- IPAddress subnet(255,255,255,0);
- delay(500);
- WiFi.softAPConfig(localIp, gateway, subnet);
- String macString = parseMAC(WiFi.macAddress());
- macString = "ESP_MM_"+macString;
- macString.toCharArray(ssid_char, macString.length()+1);
- WiFi.softAP(ssid_char);
- Serial.println("WiFi AP set! " + macString);
- WiFi_mode = "APSTA";
- connectWiFi();
- }
- else
- {
- Serial.println("Reading Memory - Memory found, Setting to STATION mode");
- WiFi_mode = "STA";
- WiFi.mode(WIFI_STA);
- connectWiFi();
- }
- // Start mainstreaming RTOS task
- xTaskCreatePinnedToCore(
- mjpegCB,
- "mjpeg",
- 2 * 1024,
- NULL,
- 2,
- &tMjpeg,
- APP_CPU);
- Serial.println("Booting Sensors");
- digitalWrite(CUSTOM_PIN, LOW);
- delay(100);
- digitalWrite(CUSTOM_PIN, HIGH);
- //setModemSleep();
- sleep_cooldown = 4;
- Serial.printf("setup complete: free heap : %d\n", ESP.getFreeHeap());
- }
- void connectWiFi()
- {
- IPAddress ip;
- delay(100);
- //WiFi.mode(WIFI_STA);
- Serial.println("Beginning connection");
- blinkLED();
- delay(100);
- WiFi.onEvent(WiFiStationConnected, SYSTEM_EVENT_STA_CONNECTED);
- WiFi.onEvent(WiFiGotIP, SYSTEM_EVENT_STA_GOT_IP);
- WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED);
- WiFi.setTxPower(WIFI_POWER_19_5dBm);
- memSSID.toCharArray(ssid_char, memSSID.length()+1);
- memPASS.toCharArray(pass_char, memPASS.length()+1);
- WiFi.begin(ssid_char, pass_char);
- Serial.print("Connecting to WiFi: "+String(ssid_char));
- while (WiFi.status() != WL_CONNECTED)
- {
- delay(500);
- Serial.print(".");
- dis_count++;
- if(dis_count>30)
- {
- Serial.println();
- Serial.println("Failed to connect - Restarting ESP");
- delay(1000);
- ESP.restart();
- }
- }
- dis_count = 0;
- ip = WiFi.localIP();
- Serial.println(F("WiFi connected"));
- Serial.println("");
- Serial.print("Stream Link: http://");
- Serial.print(ip);
- Serial.println("/");
- }
- // ==== LOOP method ==================================================================
- void loop() {
- // this seems to be necessary to let IDLE task run and do GC
- vTaskDelay(1000);
- if (sleep_status)
- {
- if (active_sec >= 3600)
- {
- Serial.println("Hourly maintanace reboot - Restarting ESP");
- delay(1000);
- ESP.restart();
- }
- esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
- Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");
- Serial.println("Going to sleep now");
- esp_camera_deinit();
- delay(100);
- digitalWrite(LED_BUILTIN, LOW);
- rtc_gpio_hold_en(GPIO_NUM_4);
- gpio_deep_sleep_hold_en();
- esp_light_sleep_start();
- Serial.println("ESP32 awake");
- rtc_gpio_hold_dis(GPIO_NUM_4);
- gpio_deep_sleep_hold_dis();
- static 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;
- config.frame_size = FRAMESIZE_XGA;
- config.jpeg_quality = 10;
- config.fb_count = 1;
- esp_err_t err = esp_camera_init(&config);
- if (err != ESP_OK) {
- Serial.println("Error initializing the camera");
- delay(1000);
- ESP.restart();
- }
- sensor_t * s = esp_camera_sensor_get();
- s->set_vflip(s, 0);//flip it back
- s->set_hmirror(s, 0);
- s->set_framesize(s, FRAMESIZE_XGA);
- active_sec += TIME_TO_SLEEP;
- WiFi.setTxPower(WIFI_POWER_19_5dBm);
- wakeModemSleep();
- sleep_cooldown = 4;
- //delay(2000);
- }
- else
- {
- //delay(1000);
- active_sec += 1;
- }
- updateStatus();
- //sendStatusToDomoticz();
- if((memMODE == "APSTA" || memMODE == "STA") && WiFi.status() != WL_CONNECTED)
- {
- if (sleep_status)
- {
- wakeModemSleep();
- }
- if (client_connected) sleep_cooldown = sleep_cooldown_base;
- else sleep_cooldown = 5;
- if (dis_count == 0)
- {
- Serial.println("Disconnected from WiFi (!)");
- WiFi.mode(WIFI_STA);
- WiFi.disconnect(true);
- delay(100);
- WiFi.begin(ssid_char, pass_char);
- Serial.println("Trying to reestablish connection");
- while (WiFi.status() != WL_CONNECTED) {
- delay(500);
- Serial.print(".");
- dis_count++;
- if(dis_count>20)
- {
- Serial.println();
- Serial.println("(FAILURE) Connection timeout - Restarting ESP");
- delay(1000);
- ESP.restart();
- }
- }
- }
- }
- if (sleep_status == false && WiFi_mode == "STA")
- {
- sleep_cooldown--;
- Serial.println("Sleep timeout in | " + String(sleep_cooldown));
- if (sleep_cooldown <= 0)
- {
- sleep_cooldown = sleep_cooldown_base;
- setModemSleep();
- }
- }
- if(WiFi.status() == WL_CONNECTED)
- {
- if (dis_count > 0)
- {
- dis_count = 0;
- Serial.println("(SUCCESS) WiFi connection reestablished");
- //digitalWrite(indicatorLED,HIGH);
- }
- }
- }
- void blinkLED() {
- digitalWrite(LED_BUILTIN, HIGH);
- delay(50);
- digitalWrite(LED_BUILTIN, LOW);
- delay(50);
- digitalWrite(LED_BUILTIN, HIGH);
- delay(50);
- digitalWrite(LED_BUILTIN, LOW);
- }
- void setModemSleep() {
- if (client_connected) {
- digitalWrite(LED_BUILTIN, HIGH);
- delay(300);
- digitalWrite(LED_BUILTIN, LOW);
- delay(200);
- digitalWrite(LED_BUILTIN, HIGH);
- delay(300);
- digitalWrite(LED_BUILTIN, LOW);
- delay(200);
- digitalWrite(LED_BUILTIN, HIGH);
- delay(300);
- digitalWrite(LED_BUILTIN, LOW);
- delay(200);
- }
- sleep_status = true;
- client_connected = false;
- LED_control_active = false;
- }
- void wakeModemSleep() {
- //#define LED_BUILTIN 4
- //pinMode(LED_BUILTIN, OUTPUT);
- if (client_connected) {
- digitalWrite(LED_BUILTIN, HIGH);
- delay(2000);
- digitalWrite(LED_BUILTIN, LOW);
- }
- Serial.println("");
- Serial.println("ESP32 active");
- sleep_status = false;
- }
- String getStatus()
- {
- String msg = "{\"batteryPercent\":" + String(batteryPercent) + ", \"batteryVoltage\":"+ String(bat_busvoltage) + ", \"batteryCurrent\":" + String(bat_current_mA) + ", \"wifiSignal\":" + String(wifi_signal) +", ";
- msg += "\"SolarVoltage\":" + String(sol_busvoltage) + ", \"SolarCurrent\":" + String(sol_current_mA) + ", \"memory\":" + String(free_memory) + ", \"active_time\":" + String(active_sec) + "}";
- return msg;
- }
- String updateStatus()
- {
- free_memory = float(ESP.getFreeHeap())/1000;
- bat_shuntvoltage = readSensor(ina219_bat.getShuntVoltage_mV());
- bat_busvoltage = readSensor(ina219_bat.getBusVoltage_V());
- bat_current_mA = readSensor(ina219_bat.getCurrent_mA());
- bat_power_mW = readSensor(ina219_bat.getPower_mW());
- bat_loadvoltage = bat_busvoltage + (bat_shuntvoltage / 1000);
- batteryPercent = max(0, int(map(int(bat_busvoltage*100), int(5.0*100), int(8.4*100), 0, 100)));
- if (p_current == bat_current_mA) measurements++;
- else
- {
- measurements = 0;
- p_current = bat_current_mA;
- }
- if (measurements >= 10)
- {
- Serial.println("INA219 Sensor malfunctioned. Resetting...");
- digitalWrite(CUSTOM_PIN, LOW);
- delay(2000);
- digitalWrite(CUSTOM_PIN, HIGH);
- measurements = 0;
- }
- long rssi = WiFi.RSSI();
- int val = map(rssi, -100, -50, 0, 100);
- if (val > 100) val = 100;
- wifi_signal = val;
- String msg = "{\"batteryPercent\":" + String(batteryPercent) + ", \"batteryVoltage\":"+ String(bat_busvoltage) + ", \"batteryCurrent\":" + String(bat_current_mA) + ", \"wifiSignal\":"+ String(wifi_signal) +", ";
- sol_shuntvoltage = readSensor(ina219_sol.getShuntVoltage_mV());
- sol_busvoltage = readSensor(ina219_sol.getBusVoltage_V());
- sol_current_mA = readSensor(ina219_sol.getCurrent_mA());
- sol_power_mW = readSensor(ina219_sol.getPower_mW());
- sol_loadvoltage = sol_busvoltage + (sol_shuntvoltage / 1000);
- msg += "\"SolarVoltage\":" + String(sol_busvoltage) + ", \"SolarCurrent\":" + String(sol_current_mA) + ", \"memory\":" + String(free_memory) + ", \"active_time\":" + String(active_sec) + "}";
- Serial.println(msg);
- }
- void sendStatusToDomoticz()
- {
- sendHttp("http://192.168.2.5:8080/json.htm?type=command¶m=udevice&idx=86&nvalue=0&svalue=" + String(bat_busvoltage));
- sendHttp("http://192.168.2.5:8080/json.htm?type=command¶m=udevice&idx=90&nvalue=0&svalue=" + String(bat_current_mA));
- sendHttp("http://192.168.2.5:8080/json.htm?type=command¶m=udevice&idx=87&nvalue=0&svalue=" + String(sol_busvoltage));
- sendHttp("http://192.168.2.5:8080/json.htm?type=command¶m=udevice&idx=91&nvalue=0&svalue=" + String(sol_current_mA));
- sendHttp("http://192.168.2.5:8080/json.htm?type=command¶m=udevice&idx=88&nvalue=0&svalue=" + String(free_memory));
- sendHttp("http://192.168.2.5:8080/json.htm?type=command¶m=udevice&idx=89&nvalue=0&svalue=" + String(active_sec));
- }
- void sendHttp(String url)
- {
- HTTPClient http;
- http.begin(url.c_str());
- int httpResponseCode = http.GET();
- http.end();
- }
- float readSensor(float var)
- {
- if (isnan(var))
- {
- return 0;
- }
- return var;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement