From 1d928bb214bf0aeceda632f090ac1a15ff6f4652 Mon Sep 17 00:00:00 2001 From: jameszah <36938190+jameszah@users.noreply.github.com> Date: Wed, 12 Jan 2022 23:16:28 -0700 Subject: [PATCH] Jan 12, 2022 - version A1 - update for esp32-arduino 2.02 - replace ftp file transfer with http file transfer directly from browser - obscure features deleted --- vA1/CRC32.cpp | 59 + vA1/CRC32.h | 77 + vA1/ESPxWebFlMgr.cpp | 925 ++++++++++++ vA1/ESPxWebFlMgr.h | 162 ++ vA1/ESPxWebFlMgrWp.h | 541 +++++++ vA1/ESPxWebFlMgrWpF.h | 23 + vA1/TimeLapseAviA1.5x.ino | 2734 ++++++++++++++++++++++++++++++++++ vA1/UniversalTelegramBot.cpp | 968 ++++++++++++ vA1/UniversalTelegramBot.h | 129 ++ vA1/settings.h | 65 + 10 files changed, 5683 insertions(+) create mode 100644 vA1/CRC32.cpp create mode 100644 vA1/CRC32.h create mode 100644 vA1/ESPxWebFlMgr.cpp create mode 100644 vA1/ESPxWebFlMgr.h create mode 100644 vA1/ESPxWebFlMgrWp.h create mode 100644 vA1/ESPxWebFlMgrWpF.h create mode 100644 vA1/TimeLapseAviA1.5x.ino create mode 100644 vA1/UniversalTelegramBot.cpp create mode 100644 vA1/UniversalTelegramBot.h create mode 100644 vA1/settings.h diff --git a/vA1/CRC32.cpp b/vA1/CRC32.cpp new file mode 100644 index 0000000..12972ca --- /dev/null +++ b/vA1/CRC32.cpp @@ -0,0 +1,59 @@ +// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr +// based on https://github.com/holgerlembke/ESPxWebFlMgr + +// +// Copyright (c) 2013 Christopher Baker +// +// SPDX-License-Identifier: MIT +// + + +#include "CRC32.h" + +// Conditionally use pgm memory if it is available. + +#if defined(PROGMEM) + #define FLASH_PROGMEM PROGMEM + #define FLASH_READ_DWORD(x) (pgm_read_dword_near(x)) +#else + #define FLASH_PROGMEM + #define FLASH_READ_DWORD(x) (*(uint32_t*)(x)) +#endif + + +static const uint32_t crc32_table[] FLASH_PROGMEM = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c +}; + + +CRC32::CRC32() +{ + reset(); +} + + +void CRC32::reset() +{ + _state = ~0L; +} + + +void CRC32::update(const uint8_t& data) +{ + // via http://forum.arduino.cc/index.php?topic=91179.0 + uint8_t tbl_idx = 0; + + tbl_idx = _state ^ (data >> (0 * 4)); + _state = FLASH_READ_DWORD(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4); + tbl_idx = _state ^ (data >> (1 * 4)); + _state = FLASH_READ_DWORD(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4); +} + + +uint32_t CRC32::finalize() const +{ + return ~_state; +} diff --git a/vA1/CRC32.h b/vA1/CRC32.h new file mode 100644 index 0000000..a7c9fbd --- /dev/null +++ b/vA1/CRC32.h @@ -0,0 +1,77 @@ +// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr +// based on https://github.com/holgerlembke/ESPxWebFlMgr + +// +// Copyright (c) 2013 Christopher Baker +// +// SPDX-License-Identifier: MIT +// + + +#pragma once + + +#include "Arduino.h" + + +/// \brief A class for calculating the CRC32 checksum from arbitrary data. +/// \sa http://forum.arduino.cc/index.php?topic=91179.0 +class CRC32 +{ +public: + /// \brief Initialize an empty CRC32 checksum. + CRC32(); + + /// \brief Reset the checksum claculation. + void reset(); + + /// \brief Update the current checksum caclulation with the given data. + /// \param data The data to add to the checksum. + void update(const uint8_t& data); + + /// \brief Update the current checksum caclulation with the given data. + /// \tparam Type The data type to read. + /// \param data The data to add to the checksum. + template + void update(const Type& data) + { + update(&data, 1); + } + + /// \brief Update the current checksum caclulation with the given data. + /// \tparam Type The data type to read. + /// \param data The array to add to the checksum. + /// \param size Size of the array to add. + template + void update(const Type* data, size_t size) + { + size_t nBytes = size * sizeof(Type); + const uint8_t* pData = (const uint8_t*)data; + + for (size_t i = 0; i < nBytes; i++) + { + update(pData[i]); + } + } + + /// \returns the caclulated checksum. + uint32_t finalize() const; + + /// \brief Calculate the checksum of an arbitrary data array. + /// \tparam Type The data type to read. + /// \param data A pointer to the data to add to the checksum. + /// \param size The size of the data to add to the checksum. + /// \returns the calculated checksum. + template + static uint32_t calculate(const Type* data, size_t size) + { + CRC32 crc; + crc.update(data, size); + return crc.finalize(); + } + +private: + /// \brief The internal checksum state. + uint32_t _state = ~0L; + +}; diff --git a/vA1/ESPxWebFlMgr.cpp b/vA1/ESPxWebFlMgr.cpp new file mode 100644 index 0000000..ca038a9 --- /dev/null +++ b/vA1/ESPxWebFlMgr.cpp @@ -0,0 +1,925 @@ +// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr +// Jan 12, 2022 - adds dates/times to display +// based on https://github.com/holgerlembke/ESPxWebFlMgr + +#include +#include +#include "ESPxWebFlMgr.h" +#include "ESPxWebFlMgrWp.h" +#include "ESPxWebFlMgrWpF.h" + +#include "crc32.h" + +#include //jz + +#ifdef ESP8266 +#include +#include +#endif + +#ifdef ESP32 +#include +#include +#include //jz #include +#include +#endif + + +String getContentType(const String& path) { +#ifdef ESP8266 + return mime::getContentType(path); +#endif +#ifdef ESP32 + return StaticRequestHandler::getContentType(path); +#endif +} + + +//***************************************************************************************************** +ESPxWebFlMgr::ESPxWebFlMgr(word port) { + _Port = port; +} + +//***************************************************************************************************** +ESPxWebFlMgr::~ESPxWebFlMgr() { + end(); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::begin() { +#ifdef ESP8266 + fileManager = new ESP8266WebServer(_Port); +#endif +#ifdef ESP32 + fileManager = new WebServer(_Port); +#endif + +#ifdef fileManagerServerStaticsInternally + fileManager->on("/", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerIndexpage, this)); + fileManager->on("/fm.css", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerCSS, this)); + fileManager->on("/fm.js", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerJS, this)); +#endif + fileManager->on("/bg.css", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerGetBackGround, this)); + + fileManager->on("/i", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerFileListInsert, this)); + fileManager->on("/c", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerCommandExecutor, this)); + fileManager->on("/e", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerFileEditorInsert, this)); + // file receiver with attached file to form + fileManager->on("/r", HTTP_POST, std::bind(&ESPxWebFlMgr::fileManagerReceiverOK, this), + std::bind(&ESPxWebFlMgr::fileManagerReceiver, this)); + + fileManager->onNotFound(std::bind(&ESPxWebFlMgr::fileManagerNotFound, this)); + + fileManager->begin(); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::end() { + if (fileManager) { + delete fileManager; + fileManager = NULL; + } +} + +//***************************************************************************************************** +void ESPxWebFlMgr::handleClient() { + if (fileManager) { + fileManager->handleClient(); + } +} + +//***************************************************************************************************** +void ESPxWebFlMgr::setViewSysFiles(bool vsf) { + _ViewSysFiles = vsf; +} + +//***************************************************************************************************** +bool ESPxWebFlMgr::getViewSysFiles(void) { + return _ViewSysFiles; +} + +//***************************************************************************************************** +void ESPxWebFlMgr::setSysFileStartPattern(String sfsp) { + _SysFileStartPattern = sfsp; +} + +//***************************************************************************************************** +String ESPxWebFlMgr::getSysFileStartPattern(void) { + return _SysFileStartPattern; +} + + +//***************************************************************************************************** +// privates start here +//***************************************************************************************************** +//***************************************************************************************************** +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerGetBackGround(void) { + fileManager->send(200, F("text/css"), ".background {background-color: " + _backgroundColor + ";}"); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::setBackGroundColor(const String backgroundColor) { + _backgroundColor = backgroundColor; +} + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerNotFound(void) { + String uri = fileManager->uri(); + + if (uri == "/") { + uri = "/fm.html"; + } + + String contentTyp = getContentType(uri); + + if (ESPxWebFlMgr_FileSystem.exists(uri)) { + File f = ESPxWebFlMgr_FileSystem.open(uri, "r"); + if (f) { + if (fileManager->streamFile(f, contentTyp) != f.size()) { + // Serial.println(F("Sent less data than expected!")); + // We should panic a little bit. + } + f.close(); + } + } else + { + fileManager->send(404, F("text/plain"), F("URI not found.")); + } +} + +//***************************************************************************************************** +String ESPxWebFlMgr::dispIntDotted(size_t i) { + String res = ""; + while (i != 0) { + int r = i % 1000; + res = String(i % 1000) + res; + i /= 1000; + if ( (r < 100) && (i > 0) ) { + res = "0" + res; + if (r < 10) { + res = "0" + res; + } + } + if (i != 0) { + res = "," + res; //jz dot to comma ;-) + } + } + return res; +} + +//***************************************************************************************************** +size_t ESPxWebFlMgr::totalBytes(void) { +#ifdef ESP8266 + FSInfo info; + ESPxWebFlMgr_FileSystem.info(info); + return info.totalBytes; +#endif + +#ifdef ESP32 + return (ESPxWebFlMgr_FileSystem.totalBytes() / 1024); +#endif +} + +//***************************************************************************************************** +size_t ESPxWebFlMgr::usedBytes(void) { +#ifdef ESP8266 + FSInfo info; + ESPxWebFlMgr_FileSystem.info(info); + return info.usedBytes; +#endif + +#ifdef ESP32 + return (ESPxWebFlMgr_FileSystem.usedBytes() / 1024); +#endif +} + +//***************************************************************************************************** +String ESPxWebFlMgr::dispFileString(size_t fs) { + if (fs < 0) { + return "-0"; + } + + if (fs == 0) { + return "0 kB"; + } + + if (fs < 1000) { + return String(fs) + " kB"; + } + // switch from bytes to kilobytes due to 4gb+ sd cards //jz + //String units[] = { "B", "kB", "MB", "GB", "TB" }; + String units[] = { "kB", "MB", "GB", "TB" }; + int digitGroups = (int) (log10(fs) / log10(1024)); + + //return String(fs / pow(1024, digitGroups)) + " " + units[digitGroups] + " (" + dispIntDotted(fs) + " kB)"; + return String(fs / pow(1024, digitGroups)) + " " + units[digitGroups] ; +} + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerIndexpage(void) { + fileManager->send(200, F("text/html"), FPSTR(ESPxWebFlMgrWpindexpage)); + delay(1); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerJS(void) { + fileManager->send(200, F("text/javascript"), FPSTR(ESPxWebFlMgrWpjavascript)); + delay(1); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerCSS(void) { + fileManager->send(200, F("text/css"), FPSTR(ESPxWebFlMgrWpcss)); + delay(1); +} + +//***************************************************************************************************** +String ESPxWebFlMgr::CheckFileNameLengthLimit(String fn) { + // SPIFFS file name limit. Is there a way to get the max length from SPIFFS/LittleFS? + // SPIFFS_OBJ_NAME_LEN is spiffs.... but not very clean. + if (fn.length() > 32) { + int len = fn.length(); + fn.remove(29); + fn += String(len); + } + + return fn; +} + +//***************************************************************************************************** +String ESPxWebFlMgr::colorline(int i) { + if (i % 2 == 0) { + return "ccu"; + } else { + return "ccg"; + } +} + +//***************************************************************************************************** +boolean ESPxWebFlMgr::allowAccessToThisFile(const String filename) { + return ! filename.startsWith(_SysFileStartPattern); +} + + +//***************************************************************************************************** +// jz kludge for switching folders on the sd card +String subdir = "/"; + +//***************************************************************************************************** +//[make FS from esp8266 and esp32 compatible]********************************************************** +// this is the way MS DOS 3.x (?) did it with Int21 findfirst/findnext/findclose +#ifdef ESP8266 +File ESPxWebFlMgr::nextFile(Dir &dir) { + dir.next(); + return dir.openFile("r"); +} +File ESPxWebFlMgr::firstFile(Dir &dir) { + dir = ESPxWebFlMgr_FileSystem.openDir("/"); + return nextFile(dir); +} +#endif +#ifdef ESP32 +#define Dir File +File ESPxWebFlMgr::nextFile(Dir &dir) { + return dir.openNextFile(); +} +File ESPxWebFlMgr::firstFile(Dir &dir) { + dir = ESPxWebFlMgr_FileSystem.open(subdir, "r"); //jz dir = ESPxWebFlMgr_FileSystem.open("/", "r"); + return nextFile(dir); +} +#endif + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerFileListInsert(void) { // must get arg with /i to list that folder + fileManager->setContentLength(CONTENT_LENGTH_UNKNOWN); + fileManager->send(200, F("text/html"), String()); + + fileManager->sendContent(F("
")); + + bool gzipperexists = ( (ESPxWebFlMgr_FileSystem.exists("/gzipper.js.gz")) || + (ESPxWebFlMgr_FileSystem.exists("/gzipper.js")) ); + + //Serial.println(fileManager->args()); + //Serial.println(fileManager->argName(0)); + //Serial.println(fileManager->arg(0)); + + if ( (fileManager->args() == 1) && (fileManager->argName(0) == "subdir") ) { + subdir = fileManager->arg(0); + //Serial.print("Arg: "); Serial.println(fileManager->arg(0)); + + /* + if (fileManager->arg(0) == "/"){ + subdir = "/"; + } else if (subdir == "/") { + subdir = fileManager->arg(0); + } else { + subdir = fileManager->arg(0); + } + Serial.print("New subdir: "); Serial.println(subdir); + */ + } else { + subdir = "/"; + } + //Serial.print("Final subdir: "); Serial.println(subdir); + + // first file is "go to root" + + String fcd; + String direct = "ccd"; //jz bland color for directory + String fn = "/"; + fcd = "
  " + fn + " - GOTO ROOT DIR -" + "
"; + fcd += "
 " + " " + " 
"; + fcd += "
 " + dispIntDotted(0) + " 
"; + fcd += "
 "; + fcd += "  
"; + + fileManager->sendContent(fcd); + + + // List files + int i = 0; + Dir dir; + File file = firstFile(dir); + while (file) { + String fn = file.name(); + /* + Serial.print("FN: >"); + Serial.print(fn); + Serial.print("<"); + Serial.println(); + */ + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + /* + String fc = "
  " + fn + "
"; + */ + + String fc; + String nsd; + if (subdir == "/") { + nsd = "/"; + } else { + nsd = subdir + "/"; + } + if (file.isDirectory()) { + String direct = "ccd"; //jz bland color for directory + fc = "
  " + fn + " - DIR -" + "
"; + + } else { + //Serial.println(subdir); + //Serial.println(fn); + fc = "
  " + fn + "
"; + } + + time_t t= file.getLastWrite(); + struct tm * tmstruct = localtime(&t); + //Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec); + char ccz[100]; + sprintf(ccz, " %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec); + + + fc += "
 " + String(ccz) + " 
"; //jz + + // File f = dir.openFile("r"); + fc += "
 " + dispIntDotted(file.size()) + " 
"; + + fc += "
 " + " " + " "; + + // no gziped version and (zipper or gziped zipper) exists + if ( (! (fn.endsWith(".gz")) ) && gzipperexists) { + fc += " "; + } + // for editor +#ifndef fileManagerEditEverything + String contentTyp = getContentType(fn); + if ( (contentTyp.startsWith("text/")) || + (contentTyp.startsWith("application/j")) ) // boldly assume: json, javascript and everything else is edible.... +#endif + { + fc += " "; + } + + fc += "  
"; + + fileManager->sendContent(fc); + i++; + } + file = nextFile(dir); + } + + // fileManager->sendContent(""); + + String sinfo = "  Size: " + + dispFileString(totalBytes()) + + ", used: " + + dispFileString(usedBytes()); + /* + fileManager->sendContent(F(" FS blocksize: ")); + fileManager->sendContent(String(info.blockSize)); + fileManager->sendContent(F(", pageSize: ")); + fileManager->sendContent(String(info.pageSize)); + */ + fileManager->sendContent(F("
")); + + fileManager->sendContent(F("##")); + fileManager->sendContent(sinfo); + + fileManager->sendContent(""); + delay(1); +} + +//***************************************************************************************************** +String ESPxWebFlMgr::escapeHTMLcontent(String html) { + //html.replace("<","<"); + //html.replace(">",">"); + html.replace("&", "&"); + + return html; +} + +// in place editor +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerFileEditorInsert(void) { + //Serial.println("Edit"); + + if ( (fileManager->args() == 1) && (fileManager->argName(0) == "edit") ) { + + String fn = "/" + fileManager->arg(0); + if ( (! _ViewSysFiles) && (!allowAccessToThisFile(fn)) ) { + fileManager->send(404, F("text/plain"), F("Illegal.")); + return; + } + + fileManager->setContentLength(CONTENT_LENGTH_UNKNOWN); + fileManager->send(200, F("text/html"), String()); + + fileManager->sendContent(ESPxWebFlMgrWpFormIntro); + + if (ESPxWebFlMgr_FileSystem.exists(subdir + "/" + fn)) { + File f = ESPxWebFlMgr_FileSystem.open(subdir + "/" + fn, "r"); + if (f) { + do { + String l = f.readStringUntil('\n') + '\n'; + l = escapeHTMLcontent(l); + fileManager->sendContent(l); + } while (f.available()); + f.close(); + } + } + + fileManager->sendContent(ESPxWebFlMgrWpFormExtro1); + fileManager->sendContent(fn); + fileManager->sendContent(ESPxWebFlMgrWpFormExtro2); + + fileManager->sendContent(""); + } else { + fileManager->send(404, F("text/plain"), F("Illegal.")); + } + + delay(1); +} + +// Drag and Drop +// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop +// https://www.ab-heute-programmieren.de/drag-and-drop-upload-mit-html5/#Schritt_3_Eine_Datei_hochladen +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerReceiverOK(void) { + // Serial.println("fileManagerReceiverOK"); + fileManager->send(200); + delay(1); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerReceiver(void) { + // Serial.println("fileManagerReceiver"); + + HTTPUpload& upload = fileManager->upload(); + // Serial.println("Server upload Status: " + String(upload.status)); + + if (upload.status == UPLOAD_FILE_START) { + String filename = upload.filename; + if (!filename.startsWith("/")) { + filename = "/" + filename; + } + // Serial.print("handleFileUpload Name: "); + // Serial.println(filename); + + if (! ( (_ViewSysFiles) || (allowAccessToThisFile(filename)) ) ) { + filename = "/illegalfilename"; + } + + // cut length + filename = CheckFileNameLengthLimit(filename); + + fsUploadFile = ESPxWebFlMgr_FileSystem.open(subdir + filename, "w"); + } else if (upload.status == UPLOAD_FILE_WRITE) { + // Serial.print("handleFileUpload Data: "); + // Serial.println(upload.currentSize); + if (fsUploadFile) + fsUploadFile.write(upload.buf, upload.currentSize); + } else if (upload.status == UPLOAD_FILE_END) { + if (fsUploadFile) { + fsUploadFile.close(); + // fsUploadFile = NULL; + } + // Serial.print("handleFileUpload Size: "); + // Serial.println(upload.totalSize); + } + delay(1); +} + +struct __attribute__ ((__packed__)) zipFileHeader { + uint32_t signature; // 0x04034b50; + uint16_t versionneeded; + uint16_t bitflags; + uint16_t comp_method; + uint16_t lastModFileTime; + uint16_t lastModFileDate; + uint32_t crc_32; + uint32_t comp_size; + uint32_t uncompr_size; + uint16_t fname_len; + uint16_t extra_field_len; +}; + +struct __attribute__ ((__packed__)) zipDataDescriptor { + uint32_t signature; // 0x08074b50 + uint32_t crc32; + uint32_t comp_size; + uint32_t uncompr_size; +}; + +struct __attribute__ ((__packed__)) zipEndOfDirectory { + uint32_t signature; // 0x06054b50; + uint16_t nrofdisks; + uint16_t diskwherecentraldirectorystarts; + uint16_t nrofcentraldirectoriesonthisdisk; + uint16_t totalnrofcentraldirectories; + uint32_t sizeofcentraldirectory; + uint32_t ofsetofcentraldirectoryrelativetostartofarchiv; + uint16_t commentlength; +}; + +struct __attribute__ ((__packed__)) zipCentralDirectoryFileHeader { + uint32_t signature; // 0x02014b50 + uint16_t versionmadeby; + uint16_t versionneededtoextract; + uint16_t flag; + uint16_t compressionmethode; + uint16_t lastModFileTime; + uint16_t lastModFileDate; + uint32_t crc_32; + uint32_t comp_size; + uint32_t uncompr_size; + uint16_t fname_len; + uint16_t extra_len; + uint16_t comment_len; + uint16_t diskstart; + uint16_t internalfileattr; + uint32_t externalfileattr; + uint32_t relofsoflocalfileheader; + // nun filename, extra field, comment +}; + +//***************************************************************************************************** +int ESPxWebFlMgr::WriteChunk(const char* b, size_t l) { + // Serial.print(" Chunk: " + String(l) + " "); + + const char * footer = "\r\n"; + char chunkSize[11]; + sprintf(chunkSize, "%zx\r\n", l); + fileManager->client().write(chunkSize, strlen(chunkSize)); + fileManager->client().write(b, l); + fileManager->client().write(footer, 2); + + return strlen(chunkSize) + l + 2; +} + +//***************************************************************************************************** +/* https://en.wikipedia.org/wiki/Zip_(file_format) + https://www.fileformat.info/tool/hexdump.htm + https://hexed.it/?hl=de + HxD https://mh-nexus.de/de/ + + This code needs some memory: + 4 * + copybuffersize + + Uses no compression, because, well, code size. Should be good for 4mb. +*/ +void ESPxWebFlMgr::getAllFilesInOneZIP(void) { + const byte copybuffersize = 100; + + fileManager->setContentLength(CONTENT_LENGTH_UNKNOWN); + // fileManager->sendHeader(F("Content-Type"), F("text/text")); + // fileManager->sendHeader(F("Transfer-Encoding"), F("chunked")); + // fileManager->sendHeader(F("Connection"), F("close")); + fileManager->sendHeader(F("Content-Disposition"), F("attachment; filename=alles.zip")); + fileManager->sendHeader(F("Content-Transfer-Encoding"), F("binary")); + fileManager->send(200, F("application/octet-stream"), ""); + + // Pass 0: count files + int files = 0; + { + Dir dir; + File file = firstFile(dir); + while (file) { + String fn = file.name(); + if (!file.isDirectory() && (file.size() != 0)) { //jz + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + files++; + } + } + file = nextFile(dir); + } + //Serial.println("Files: " + String(files)); + } + // Store the crcs + uint32_t crc_32s[files]; + + // Pass 1: local headers + file + { + zipFileHeader zip; + zip.signature = 0x04034b50; + zip.versionneeded = 0; + zip.bitflags = 1 << 3; + zip.comp_method = 0; // stored + zip.lastModFileTime = 0x4fa5; + zip.lastModFileDate = 0xe44e; + zip.extra_field_len = 0; + + int i = 0; + Dir dir; + File file = firstFile(dir); + while (file) { + String fn = file.name(); + if (!file.isDirectory() && (file.size() != 0) ) { //jz + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + if (fn.indexOf("/") == 0) { + fn.remove(0, 1); // "/" filenames beginning with "/" dont work for Windows.... + } + + zip.comp_size = 0; + zip.uncompr_size = 0; + zip.crc_32 = 0; + zip.fname_len = fn.length(); + WriteChunk((char*)&zip, sizeof(zip)); + WriteChunk(fn.c_str(), zip.fname_len); + + //Serial.print("Send: " + fn); + // File f = dir.open("r",FILE_READ); + int len = file.size(); + //Serial.print("\nsending "); Serial.print(fn); + //Serial.print(" len is "); Serial.println(len); + + // send crc+len later... + zipDataDescriptor datadiscr; + datadiscr.signature = 0x08074b50; + datadiscr.comp_size = len; + datadiscr.uncompr_size = len; + + const char * footer = "\r\n"; + char chunkSize[11]; + sprintf(chunkSize, "%zx\r\n", len); + fileManager->client().write(chunkSize, strlen(chunkSize)); + + { // pff. + CRC32 crc; + byte b[copybuffersize]; + int lenr = len; + while (lenr > 0) { + byte r = (lenr > copybuffersize) ? copybuffersize : lenr; + file.read(b, r); + crc.update(b, r); + fileManager->client().write(b, r); + lenr -= r; + // Serial.print(lenr);Serial.print(","); //jz + } + //Serial.println(" done"); + datadiscr.crc32 = crc.finalize(); + crc_32s[i] = datadiscr.crc32; + } + + fileManager->client().write(footer, 2); + + WriteChunk((char*)&datadiscr, sizeof(datadiscr)); + + // f.close(); + i++; + /** / + Serial.print(" "); + Serial.print(l); + Serial.println(); + /**/ + } + } + file = nextFile(dir); + } + + } + + // Pass 2: Central directory Structur + { + zipEndOfDirectory eod; + eod.signature = 0x06054b50; + eod.nrofdisks = 0; + eod.diskwherecentraldirectorystarts = 0; + eod.nrofcentraldirectoriesonthisdisk = 0; + eod.totalnrofcentraldirectories = 0; + eod.sizeofcentraldirectory = 0; + eod.ofsetofcentraldirectoryrelativetostartofarchiv = 0; + eod.commentlength = 0; + + zipCentralDirectoryFileHeader CDFH; + + CDFH.signature = 0x02014b50; + CDFH.versionmadeby = 0; + CDFH.versionneededtoextract = 0; + CDFH.flag = 0; + CDFH.compressionmethode = 0; // Stored + CDFH.lastModFileTime = 0x4fa5; + CDFH.lastModFileDate = 0xe44e; + CDFH.extra_len = 0; + CDFH.comment_len = 0; + CDFH.diskstart = 0; + CDFH.internalfileattr = 0x01; + CDFH.externalfileattr = 0x20; + CDFH.relofsoflocalfileheader = 0; + + int i = 0; + + Dir dir; + File file = firstFile(dir); + while (file) { + String fn = file.name(); + if (!file.isDirectory() && (file.size() != 0)) { //jz + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + if (fn.indexOf("/") == 0) { + fn.remove(0, 1); // "/" filenames beginning with "/" dont work for Windows.... + } + // Serial.print("CDFH: " + fn); + // File f = dir.open("r",FILE_READ); + int len = file.size(); + + //Serial.print("\nsending "); Serial.print(fn); //jz + //Serial.print(" len is "); Serial.println(len); + + CDFH.comp_size = len; + CDFH.uncompr_size = len; + CDFH.fname_len = fn.length(); + CDFH.crc_32 = crc_32s[i]; + + // f.close(); + + WriteChunk((char*)&CDFH, sizeof(CDFH)); + WriteChunk(fn.c_str(), CDFH.fname_len); + + int ofs = sizeof(zipFileHeader) + len + CDFH.fname_len + sizeof(zipDataDescriptor); + + // next position + CDFH.relofsoflocalfileheader += ofs; + + // book keeping + eod.nrofcentraldirectoriesonthisdisk++; + eod.totalnrofcentraldirectories++; + eod.ofsetofcentraldirectoryrelativetostartofarchiv += ofs; + eod.sizeofcentraldirectory += sizeof(CDFH) + CDFH.fname_len; + + i++; + } + } + file = nextFile(dir); + } + + // Serial.print("EOD: "); + WriteChunk((char*)&eod, sizeof(eod)); + // Serial.println(); + } + + const char * endchunk = "0\r\n\r\n"; + fileManager->client().write(endchunk, 5); + + fileManager->sendContent(""); + delay(1); +} + +//***************************************************************************************************** +void ESPxWebFlMgr::fileManagerCommandExecutor(void) { + // https://www.youtube.com/watch?v=KSxTxynXiBs + /* + for (uint8_t i = 0; i < fileManager->args(); i++) { + Serial.print(i); + Serial.print(" "); + Serial.print(fileManager->argName(i)); + Serial.print(": "); + Serial.print(fileManager->arg(i)); + Serial.println(); + } + */ + + // no Args: DIE! + if (fileManager->args() == 0) { + return; + } + + // +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+ + // one arg, "za", zip all and download + if ( (fileManager->args() == 1) && (fileManager->argName(0) == "za") ) { + getAllFilesInOneZIP(); + // does it all + return; + } + + // +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+ + // one arg, "dwn", download + // must happen in the context of the webpage... thus via "window.location.href="/c?dwn="+filename;" + if ( (fileManager->args() == 1) && (fileManager->argName(0) == "dwn") ) { + String fn = fileManager->arg(0); + Serial.println(fn); + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + //Serial.println("allowed"); + File f = ESPxWebFlMgr_FileSystem.open("/" + fn, "r"); // add slash for esp32_arduino 2.0 + if (f) { + //Serial.println("got it open"); + fileManager->sendHeader(F("Content-Type"), F("text/text")); + fileManager->sendHeader(F("Connection"), F("close")); + //Serial.print(">");Serial.print(fn);Serial.println("<"); + //Serial.println(fn.indexOf("/")); + if (fn.indexOf("/") == 0) { + fileManager->sendHeader(F("Content-Disposition"), "attachment; filename=" + fn.substring(1)); + } else { + fileManager->sendHeader(F("Content-Disposition"), "attachment; filename=" + fn); + } + fileManager->sendHeader(F("Content-Transfer-Encoding"), F("binary")); + if (fileManager->streamFile(f, "application/octet-stream") != f.size()) { + Serial.println(F("Sent less data than expected!")); + } + f.close(); + return; + } else { + Serial.print("Could not open file "); Serial.println(fn); + } + } + } + + // +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+ + // one arg, "opd", opendirectory + if ( (fileManager->args() == 1) && (fileManager->argName(0) == "opd") ) { + String fn = fileManager->arg(0); + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + delay(1); + } + } + + // +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+ + // one arg, "del", delete + if ( (fileManager->args() == 1) && (fileManager->argName(0) == "del") ) { + String fn = fileManager->arg(0); + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + int x = ESPxWebFlMgr_FileSystem.remove( subdir + "/" + fn); // Add slash + //delay(1000); + if (!x) { + Serial.print("remove failed, try rmdir "); + Serial.print( subdir + "/" + fn ); + int y = ESPxWebFlMgr_FileSystem.rmdir( "/" + fn); // Add slash + //delay(1000); + if (!y) { + Serial.print("rmdir failed, directory must be empty! "); + } + } + } + } + + // +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+ + // one arg, "ren", rename + if ( (fileManager->args() == 2) && (fileManager->argName(0) == "ren") ) { + String fn = fileManager->arg(0); + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) { + String fn2 = CheckFileNameLengthLimit(fileManager->arg(1)); + if ( (_ViewSysFiles) || (allowAccessToThisFile(fn2)) ) { + Serial.println(subdir); + Serial.println(fn); + Serial.println(fn2); + ESPxWebFlMgr_FileSystem.rename( subdir + "/" + fn, subdir + "/" + fn2); + } + } + } + + // dummy answer + fileManager->send(200, "text/plain", ""); + delay(1); +} diff --git a/vA1/ESPxWebFlMgr.h b/vA1/ESPxWebFlMgr.h new file mode 100644 index 0000000..42e5d36 --- /dev/null +++ b/vA1/ESPxWebFlMgr.h @@ -0,0 +1,162 @@ +// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr +// Jan 12, 2022 - adds dates/times to display +// based on https://github.com/holgerlembke/ESPxWebFlMgr + +// inline guard. Did I mention that c/c++ is broken by design? +#ifndef ESPxWebFlMgr_h +#define ESPxWebFlMgr_h + +/* + Changes + V1.03 + x removed all SPIFFS from ESP32 version, switched fully to LittleFS + x fixed rename+delete for ESP32+LittleFS (added "/") + + V1.02 + x fixed the way to select the file system by conditional defines + + V1.01 + + added file name progress while uploading + x fixed error in ZIP file structure (zip.bitflags needs a flag) + + V1.00 + + out of V0.9998... + + ESP8266: LittleFS is default + + javascript: added "msgline();" + + javascript: added "Loading..." as a not-working-hint to show that Javascript is disabled + + cleaning up the "/"-stuff (from SPIFF with leading "/" to LittleFS without...) + + Warning: esp8266 2.7.4 has an error in mime::getContentType(path) for .TXT. Fix line 65 is { kTxtSuffix, kTxt }, + + review of "edit file", moved some stuff to ESPxWebFlMgrWpF.h +*/ + +#include +#include + +// file system default for esp8266 is LittleFS, for ESP32 it is SPIFFS (no time to check...) + +#ifdef ESP8266 + #include + #include + #include + // + #include + #define ESPxWebFlMgr_FileSystem LittleFS + /* + #include + #define ESPxWebFlMgr_FileSystem SPIFFS + */ +#endif + +#ifdef ESP32 + #include + #include + #include + #include //jz #include + #define ESPxWebFlMgr_FileSystem SD_MMC //jz #define ESPxWebFlMgr_FileSystem LittleFS +#endif + + +#ifndef ESPxWebFlMgr_FileSystem +#pragma message ("ESPxWebFlMgr_FileSystem not defined.") +#endif + +/* undefine this to save about 10k code space. + it requires to put the files from "/filemanager" into the FS. No free lunch. +*/ +#define fileManagerServerStaticsInternally + +// will show the Edit-Button for every file type, even binary and such. +//#define fileManagerEditEverything + +class ESPxWebFlMgr { + private: + word _Port ; +#ifdef ESP8266 + ESP8266WebServer * fileManager = NULL; +#endif +#ifdef ESP32 + WebServer * fileManager = NULL; +#endif + bool _ViewSysFiles = false; + String _SysFileStartPattern = "/."; + File fsUploadFile; + String _backgroundColor = "black"; + + void fileManagerNotFound(void); + String dispIntDotted(size_t i); + String dispFileString(size_t fs); + String CheckFileNameLengthLimit(String fn); + + // the webpage + void fileManagerIndexpage(void); + void fileManagerJS(void); + void fileManagerCSS(void); + void fileManagerGetBackGround(void); + + // javascript xmlhttp includes + String colorline(int i); + String escapeHTMLcontent(String html); + void fileManagerFileListInsert(void); + void fileManagerFileEditorInsert(void); + boolean allowAccessToThisFile(const String filename); + void fileManagerCommandExecutor(void); + void fileManagerReceiverOK(void); + void fileManagerReceiver(void); + + // Zip-File uncompressed/stored + void getAllFilesInOneZIP(void); + int WriteChunk(const char* b, size_t l); + + // helper: fs.h from esp32 and esp8266 don't have a compatible solution + // for getting a file list from a directory +#ifdef ESP32 +#define Dir File +#endif + File nextFile(Dir &dir); + File firstFile(Dir &dir); + // and not to get this data about usage... + size_t totalBytes(void); + size_t usedBytes(void); + + public: + ESPxWebFlMgr(word port); + virtual ~ESPxWebFlMgr(); + + void begin(); + void end(); + virtual void handleClient(); + + // This must be called before the webpage is loaded in the browser... + // must be a valid css color name, see https://en.wikipedia.org/wiki/Web_colors + void setBackGroundColor(const String backgroundColor); + + void setViewSysFiles(bool vsf); + bool getViewSysFiles(void); + + void setSysFileStartPattern(String sfsp); + String getSysFileStartPattern(void); +}; + +#endif + +/* + History + + -- 2019-07-07 + + Renamed to ESPxWebFlMgr and made it work with esp32 and esp8266 + + separated file manager web page, "build script" to generate it + + -- 2019-07-06 + + "Download all files" creates a zip file from all files and downloads it + + option to set background color + - html5 fixes + + -- 2019-07-03 + + Public Release on https://github.com/holgerlembke/ESP8266WebFlMgr + + + Things to do + + ?? unify file system access for SPIFFS, LittleFS and SDFS + +*/ diff --git a/vA1/ESPxWebFlMgrWp.h b/vA1/ESPxWebFlMgrWp.h new file mode 100644 index 0000000..e04d9c5 --- /dev/null +++ b/vA1/ESPxWebFlMgrWp.h @@ -0,0 +1,541 @@ +// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr +// Jan 12, 2022 - adds dates/times to display +// based on https://github.com/holgerlembke/ESPxWebFlMgr + +// inline guard. Did I mention that c/c++ is broken by design? +#ifndef ESPxWebFlMgrWp_h +#define ESPxWebFlMgrWp_h + +// this file has been created by makeESPxWebFlMgrWp\do.cmd + +//***************************************************************************************************** +static const char ESPxWebFlMgrWpindexpage[] PROGMEM = R"==x==( + + + + FileManager + + + + + + + +
+
 
+
 
+
 
+
 
+ +
+
 
+
+
 
+
+
+
+ File
+ Drop
+ Zone
+
+
+
 
+
+
 
+
+
+ +
 
+
Download all files
+
Loading...
+
 
+
+ File list should appear here. +
+
+ + + + )==x=="; + +static const char ESPxWebFlMgrWpjavascript[] PROGMEM = R"==x==( + +function compressurlfile(source) { + msgline("Fetching file..."); + var request = new XMLHttpRequest(); + request.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + var data = this.responseText; + var gzip = require('gzip-js'), options = { level: 9, name: source, timestamp: parseInt(Date.now() / 1000, 10) }; + var out = gzip.zip(data, options); + var bout = new Uint8Array(out); // out is 16 bits... + + msgline("Sending compressed file..."); + var sendback = new XMLHttpRequest(); + sendback.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + getfileinsert(); + } + }; + sendback.open('POST', '/r'); + var formdata = new FormData(); + var blob = new Blob([bout], { type: "application/octet-binary" }); + formdata.append(source + '.gz', blob, source + '.gz'); + sendback.send(formdata); + } + }; + request.open('GET', source, true); + request.send(null); +} + +var subdir; + +function getfileinsert() { + msgline("Fetching files infos..."); + subdir = '/'; + var request = new XMLHttpRequest(); + request.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + var res = this.responseText.split("##"); + document.getElementById('fi').innerHTML = res[0]; + document.getElementById("o3").innerHTML = res[1]; + msgline(""); + } + }; + request.open('GET', '/i', true); + request.send(null); +} + +function getfileinsert2(strddd) { + msgline("Fetching files infos..."); + subdir = strddd; + var request = new XMLHttpRequest(); + request.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + var res = this.responseText.split("##"); + document.getElementById('fi').innerHTML = res[0]; + document.getElementById("o3").innerHTML = res[1]; + msgline(""); + } + }; + request.open('GET', '/i?subdir=' + strddd, true); // must send the subdir variable to get that folder //jz + request.send(null); +} +function executecommand(command) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + getfileinsert2(subdir); + } + }; + xhr.open('GET', '/c?' + command, true); + xhr.send(null); +} + +function downloadfile(filename) { + window.location.href = "/c?dwn=" + filename; +} + +function opendirectory(filename) { + + getfileinsert2(filename); +} + +function deletefile(filename) { + if (confirm("Really delete " + filename)) { + msgline("Refresh when done deleting..."); //jz msgline("Please wait. Delete in progress..."); + executecommand("del=" + filename); + } +} + +function renamefile(filename) { + var newname = prompt("new name for " + filename, filename); + if (newname != null) { + msgline("Refresh when done renaming ..."); //jz msgline("Please wait. Rename in progress..."); + executecommand("ren=" + filename + "&new=" + newname); + } +} + +var editxhr; + +function editfile(filename) { + msgline("Please wait. Creating editor..."); + + editxhr = new XMLHttpRequest(); + editxhr.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + document.getElementById('fi').innerHTML = editxhr.responseText; + document.getElementById("o3").innerHTML = "Edit " + filename; + msgline(""); + } + }; + editxhr.open('GET', '/e?edit=' + filename, true); + editxhr.send(null); +} + +function sved(filename) { + var content = document.getElementById('tect').value; + // utf-8 + content = unescape(encodeURIComponent(content)); + + var xhr = new XMLHttpRequest(); + + xhr.open("POST", "/r", true); + + var boundary = '-----whatever'; + xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + + var body = "" + + '--' + boundary + '\r\n' + + 'Content-Disposition: form-data; name="uploadfile"; filename="' + filename + '"' + '\r\n' + + 'Content-Type: text/plain' + '\r\n' + + '' + '\r\n' + + content + '\r\n' + + '--' + boundary + '--\r\n' + // \r\n fixes upload delay in ESP8266WebServer + ''; + + // ajax does not do xhr.setRequestHeader("Content-length", body.length); + + xhr.onreadystatechange = function () { + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + getfileinsert(); + } + } + + xhr.send(body); +} + +function abed() { + getfileinsert(); +} + +var uploaddone = true; // hlpr for multiple file uploads + +function uploadFile(file, islast) { + uploaddone = false; + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + // console.log(xhr.status); + var DONE = this.DONE || 4; + if (this.readyState === DONE) { + if (islast) { + getfileinsert2(subdir); + console.log('last file'); + } + uploaddone = true; + } + }; + xhr.open('POST', '/r'); + var formdata = new FormData(); + //var newname = subdir + '/' + file.name; //jz didnt work, so do it in c++ + //file.name = newname; + formdata.append('uploadfile', file); + // not sure why, but with that the upload to esp32 is stable. + formdata.append('dummy', 'dummy'); + xhr.send(formdata); +} + +var globaldropfilelisthlpr = null; // read-only-list, no shift() +var transferitem = 0; +var uploadFileProzessorhndlr = null; + +function uploadFileProzessor() { + if (uploaddone) { + if (transferitem==globaldropfilelisthlpr.length) { + clearInterval(uploadFileProzessorhndlr); + } else { + var file = globaldropfilelisthlpr[transferitem]; + msgline("Please wait. Transferring file "+file.name+"..."); + console.log('process file ' + file.name); + transferitem++; + uploadFile(file,transferitem==globaldropfilelisthlpr.length); + } + } +} + +/* +function dropHandlerALT(ev) { + console.log('File(s) dropped'); + + document.getElementById('msg').innerHTML = "Please wait. Transferring file..."; + + // Prevent default behavior (Prevent file from being opened) + ev.preventDefault(); + + if (ev.dataTransfer.items) { + // Use DataTransferItemList interface to access the file(s) + for (var i = 0; i < ev.dataTransfer.items.length; i++) { + // If dropped items aren't files, reject them + if (ev.dataTransfer.items[i].kind === 'file') { + var file = ev.dataTransfer.items[i].getAsFile(); + uploadFile(file); + console.log('.1. file[' + i + '].name = ' + file.name); + } + } + } else { + // Use DataTransfer interface to access the file(s) + for (var i = 0; i < ev.dataTransfer.files.length; i++) { + console.log('.2. file[' + i + '].name = ' + ev.dataTransfer.files[i].name); + } + } +} +*/ + +function dropHandler(ev) { + console.log('File(s) dropped'); + + globaldropfilelisthlpr = ev.dataTransfer; + transferitem = 0; + + msgline("Please wait. Transferring file..."); + + // Prevent default behavior (Prevent file from being opened) + ev.preventDefault(); + + if (ev.dataTransfer.items) { + var data = ev.dataTransfer; + globaldropfilelisthlpr = data.files; + uploadFileProzessorhndlr = setInterval(uploadFileProzessor,1000); + console.log('Init upload list.'); + } else { + // Use DataTransfer interface to access the file(s) + for (var i = 0; i < ev.dataTransfer.files.length; i++) { + console.log('.2. file[' + i + '].name = ' + ev.dataTransfer.files[i].name); + } + } +} + +function dragOverHandler(ev) { + console.log('File(s) in drop zone'); + + // Prevent default behavior (Prevent file from being opened) + ev.preventDefault(); +} + +function msgline(msg) { + document.getElementById('msg').innerHTML = msg; +} + +function downloadall() { + msgline("Sending all files in one zip."); + window.location.href = "/c?za=all"; + msgline(""); +} + +//-> +window.onload = getfileinsert; + + )==x=="; + + +//***************************************************************************************************** +static const char ESPxWebFlMgrWpcss[] PROGMEM = R"==g==( + +div { + margin: 1px; + padding: 0px; + font-family: 'Segoe UI', Verdana, sans-serif; +} + +#gc { + display: grid; + grid-template-columns: 80px 25% auto 30px; + grid-template-rows: 20px 30px auto 30px 20px; + grid-template-areas: "o1 o2 o3 o4" "m1 c c c" "m2 c c c" "m3 c c c" "u1 u2 u3 u4"; +} + +.o1 { + grid-area: o1; + background-color: #9999CC; + border-top-left-radius: 20px; + margin-bottom: 0px; +} + +.o2 { + grid-area: o2; + background-color: #9999FF; + margin-bottom: 0px; +} + +.o3 { + grid-area: o3; + background-color: #CC99CC; + margin-bottom: 0px; + white-space: nowrap; +} + +.o4 { + grid-area: o4; + background-color: #CC6699; + border-radius: 0 10px 10px 0; + margin-bottom: 0px; +} + +.m1 { + grid-area: m1; + margin-top: 0px; + background-color: #9999CC; + display: grid; + grid-template-columns: 60px 20px; + grid-template-rows: 20px; + grid-template-areas: "s11 s12"; +} + +.s12 { + margin: 0px; + background-color: #9999CC; +} + +.s13 { + margin: 0px; + border-top-left-radius: 20px; + height: 30px; +} + +.m2 { + display: flex; + justify-content: center; + align-items: center; + grid-area: m2; + background-color: #CC6699; + width: 60px; +} + +.m3 { + grid-area: m3; + margin-bottom: 0px; + background-color: #9999CC; + display: grid; + grid-template-columns: 60px 20px; + grid-template-rows: 20px; + grid-template-areas: "s31 s32"; +} + +.s32 { + margin: 0px; + background-color: #9999CC; +} + +.s33 { + margin: 0px; + border-bottom-left-radius: 20px; + height: 30px; +} + +.u1 { + grid-area: u1; + background-color: #9999CC; + border-bottom-left-radius: 20px; + margin-top: 0px; +} + +.u2 { + grid-area: u2; + cursor: pointer; + background-color: #CC6666; + margin-top: 0px; + padding-left: 10px; + vertical-align: middle; + font-size: 80%; +} + +.u2:hover { + background-color: #9999FF; + color: white; +} + +.u3 { + grid-area: u3; + padding-left: 10px; + background-color: #FF9966; + font-size: 80%; + margin-top: 0px; +} + +.u4 { + grid-area: u4; + background-color: #FF9900; + border-radius: 0 10px 10px 0; + margin-top: 0px; +} + +.c { + grid-area: c; +} + +#fi .b { + background-color: Transparent; + border: 1px solid #9999FF; + border-radius: 1px; + padding: 0px; + width: 30px; + cursor: pointer; +} + +#fi .b:hover { + background-color: #9999FF; + color: white; +} + +.cc { + width: min-content; + margin: 10px 0px; +} + +.gc div { + padding: 1px; +} + +.ccg { + height: 1.5em; + background-color: #A5A5FF; +} + +.ccu { + height: 1.5em; + background-color: #FE9A00; +} + +.ccd { + height: 1.5em; + background-color: #e8e2d8; +} + +.ccl { + border-radius: 5px 0 0 5px; + cursor: pointer; +} + +.ccl:hover { + border-radius: 5px 0 0 5px; + color: white; + cursor: pointer; +} + +.ccr { + border-radius: 0 5px 5px 0; +} + +.cct { + text-align: right; +} + +.ccz { + text-align: right; +} + +.gc { + display: grid; + grid-template-columns: repeat(4, max-content); +} + )==g=="; + + +#endif diff --git a/vA1/ESPxWebFlMgrWpF.h b/vA1/ESPxWebFlMgrWpF.h new file mode 100644 index 0000000..1a4cb44 --- /dev/null +++ b/vA1/ESPxWebFlMgrWpF.h @@ -0,0 +1,23 @@ +// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr +// based on https://github.com/holgerlembke/ESPxWebFlMgr + +// inline guard. Still broken by design? +#ifndef ESPxWebFlMgrWpF_h +#define ESPxWebFlMgrWpF_h + +static const char ESPxWebFlMgrWpFormIntro[] PROGMEM = +R"==x==(
  + +)==x=="; + + + +#endif diff --git a/vA1/TimeLapseAviA1.5x.ino b/vA1/TimeLapseAviA1.5x.ino new file mode 100644 index 0000000..48d3be6 --- /dev/null +++ b/vA1/TimeLapseAviA1.5x.ino @@ -0,0 +1,2734 @@ +#include + +/* + + TimeLapseAvi + + ESP32-CAM Video Recorder + + This program records an AVI video on the SD Card of an ESP32-CAM. + + by James Zahary July 20, 2019 TimeLapseAvi23x.ino + jamzah.plc@gmail.com + + https://github.com/jameszah/ESP32-CAM-Video-Recorder + + jameszah/ESP32-CAM-Video-Recorder is licensed under the + GNU General Public License v3.0 + + The is Arduino code, with standard setup for ESP32-CAM + - Board ESP32 Wrover Module + - Partition Scheme Huge APP (3MB No OTA) + + Version A1 - Jan 1, 2022 + - switch from ftp to http file tranfer + https://github.com/jameszah/ESPxWebFlMgr/tree/master/esp32_sd_file_manager + - changes for esp32-arduino 1.06 -> 2.02 + - added link to telegram to download completed avi with one-click + - delete low-voltage handler (serves no purpose) + - get rid of touch (not much used i think) + + Version 99 - Aug 6, 2021 and Sep 01, 2021 + - update for new esp32-arduino library 1.05 (and 1.06) + - change framesize constants, and add HD framesize + - update ssl for telegram library (insecure and empty string) + + not yet added + - get rid of queuing system - does not add much + - switch from posix to regular esp sd library to prevent broken avi files + - get rid of rtc_cntl.h now that 1.05 is out ... but still not in 1.05 + - switch to current Brian Lough telegram library instead of including modified version + - add some javascript to make the webpage simpler (stop/start/snapshot...) + + Version 98x-WiFiMan - Jan 6, 2021 + - same a 98, but with WiFiManager to set ssid and password + - must use WiFiManager at version 2.0.3-alpha + - must reboot after setting ssid + + Version 98 - Sep 29, 2020 + - delete_old_files ... so the SD never fills up ... + + Version 97 - Sep 10, 2020 + - big endian rewrite + - speed up another_avi write routine + - add compile directives to clip out code you do not need + - telegram + - pir and touch + - touch only + - ftp + + + /* + +Using library ESP32 at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\ESP32 +Using library EEPROM at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\EEPROM +Using library WiFi at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\WiFi +Using library WiFiManager at version 2.0.5-beta in folder: C:\ArduinoPortable\arduino-1.8.19\portable\sketchbook\libraries\WiFiManager +Using library Update at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\Update +Using library WebServer at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\WebServer +Using library DNSServer at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\DNSServer +Using library ESPmDNS at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\ESPmDNS +Using library WiFiClientSecure at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\WiFiClientSecure +Using library ArduinoJson at version 6.18.5 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\sketchbook\libraries\ArduinoJson +Using library FS at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\FS +Using library SD_MMC at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\SD_MMC +Using library HTTPClient at version 2.0.0 in folder: C:\ArduinoPortable\arduino-1.8.19\portable\packages\esp32\hardware\esp32\2.0.2\libraries\HTTPClient +"C:\\ArduinoPortable\\arduino-1.8.19\\portable\\packages\\esp32\\tools\\xtensa-esp32-elf-gcc\\gcc8_4_0-esp-2021r2/bin/xtensa-esp32-elf-size" -A "C:\\Users\\James\\AppData\\Local\\Temp\\arduino_build_546686/TimeLapseAviA1.5.ino.elf" +Sketch uses 1193637 bytes (37%) of program storage space. Maximum is 3145728 bytes. +Global variables use 52980 bytes (16%) of dynamic memory, leaving 274700 bytes for local variables. Maximum is 327680 bytes. + + +*/ + + +static const char vernum[] = "vA1"; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// edit parameters for wifi name, startup parameters in the local file settings.h +#include "settings.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +int count_avi = 0; +int count_cam = 0; +int count_ftp = 0; +int count_ftp2 = 0; +int count_loop = 0; +int new_config = 5; // this system abandoned ! +int xlength = total_frames_config * capture_interval / 1000; +int repeat = repeat_config; // repeat_config declared in settings +int total_frames = total_frames_config; +int recording = 0; +int PIRstatus = 0; +int PIRrecording = 0; +int ready = 0; + +// eprom stuff v87 + +#include + +struct eprom_data { + int eprom_good; + int Internet_Enabled; + int DeepSleepPir; + int record_on_reboot; + int PIRpin; + int PIRenabled; + int framesize; + int repeat; + int xspeed; + int gray; + int quality; + int capture_interval; + int total_frames; + int xlength; + int EnableBOT; + +}; + + + +//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#include "esp_log.h" +#include "esp_http_server.h" +#include "esp_camera.h" + +// v98x-WiFiMan +// Workaround for the WebServer.h vs esp_http_server.h problem + +#define _HTTP_Method_H_ + +typedef enum { + jHTTP_GET = 0b00000001, + jHTTP_POST = 0b00000010, + jHTTP_DELETE = 0b00000100, + jHTTP_PUT = 0b00001000, + jHTTP_PATCH = 0b00010000, + jHTTP_HEAD = 0b00100000, + jHTTP_OPTIONS = 0b01000000, + jHTTP_ANY = 0b01111111, +} HTTPMethod; + +#include +#include +#include +bool InternetFailed = false; + +#ifdef include_telegram +#include +#include "UniversalTelegramBot.h" +WiFiClientSecure client; +UniversalTelegramBot bot(BOTtoken, client); +#endif + +int diskspeed = 0; +char fname[130]; +int send_a_telegram = 0; +int Wait_for_bot = 0; + +#include + +#ifdef include_ftp +#include "ESPxWebFlMgr.h" +const word filemanagerport = 8080; +ESPxWebFlMgr filemgr(filemanagerport); +#endif + +#include + + +// Time +#include "time.h" + +// MicroSD +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "esp_vfs_fat.h" +#include + +long current_millis; +long last_capture_millis = 0; +static esp_err_t cam_err; +static esp_err_t card_err; +char strftime_buf[64]; +char strftime_buf2[12]; + +int file_number = 0; +bool internet_connected = false; +struct tm timeinfo; +time_t now; + +char *filename ; +char *stream ; +int newfile = 0; +int frames_so_far = 0; +FILE *myfile; +long bp; +long ap; +long bw; +long aw; +long totalp; +long totalw; +float avgp; +float avgw; +int overtime_count = 0; +unsigned long nothing_cam = 0; +unsigned long nothing_avi = 0; + +// CAMERA_MODEL_AI_THINKER +#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 + + +// GLOBALS +#define BUFFSIZE 512 + +// global variable used by these pieces + +char str[20]; +uint16_t n; +uint8_t buf[BUFFSIZE]; + +static int i = 0; +uint8_t temp = 0, temp_last = 0; +unsigned long fileposition = 0; +uint16_t frame_cnt = 0; +uint16_t remnant = 0; +uint32_t length = 0; +uint32_t startms; +uint32_t elapsedms; +uint32_t uVideoLen = 0; +bool is_header = false; +long bigdelta = 0; +int other_cpu_active = 0; +int skipping = 0; +int skipped = 0; +int bad_jpg = 0; +int extend_jpg = 0; +int normal_jpg = 0; + +int fb_max = 12; + +camera_fb_t * fb_q[30]; +int fb_in = 0; +int fb_out = 0; + +camera_fb_t * fb = NULL; + +FILE *avifile = NULL; +FILE *idxfile = NULL; + + +#define AVIOFFSET 240 // AVI main header length + +unsigned long movi_size = 0; +unsigned long jpeg_size = 0; +unsigned long idx_offset = 0; + +uint8_t zero_buf[4] = {0x00, 0x00, 0x00, 0x00}; +uint8_t dc_buf[4] = {0x30, 0x30, 0x64, 0x63}; // "00dc" +uint8_t avi1_buf[4] = {0x41, 0x56, 0x49, 0x31}; // "AVI1" +uint8_t idx1_buf[4] = {0x69, 0x64, 0x78, 0x31}; // "idx1" + +uint8_t vga_w[2] = {0x80, 0x02}; // 640 +uint8_t vga_h[2] = {0xE0, 0x01}; // 480 +uint8_t cif_w[2] = {0x90, 0x01}; // 400 +uint8_t cif_h[2] = {0x28, 0x01}; // 296 +uint8_t svga_w[2] = {0x20, 0x03}; // 800 +uint8_t svga_h[2] = {0x58, 0x02}; // 600 +uint8_t hd_w[2] = {0x00, 0x05}; // 1280 +uint8_t hd_h[2] = {0xD0, 0x02}; // 720 +uint8_t uxga_w[2] = {0x40, 0x06}; // 1600 +uint8_t uxga_h[2] = {0xB0, 0x04}; // 1200 + + +const int avi_header[AVIOFFSET] PROGMEM = { + 0x52, 0x49, 0x46, 0x46, 0xD8, 0x01, 0x0E, 0x00, 0x41, 0x56, 0x49, 0x20, 0x4C, 0x49, 0x53, 0x54, + 0xD0, 0x00, 0x00, 0x00, 0x68, 0x64, 0x72, 0x6C, 0x61, 0x76, 0x69, 0x68, 0x38, 0x00, 0x00, 0x00, + 0xA0, 0x86, 0x01, 0x00, 0x80, 0x66, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x49, 0x53, 0x54, 0x84, 0x00, 0x00, 0x00, + 0x73, 0x74, 0x72, 0x6C, 0x73, 0x74, 0x72, 0x68, 0x30, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x73, + 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x66, + 0x28, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x18, 0x00, 0x4D, 0x4A, 0x50, 0x47, 0x00, 0x84, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4E, 0x46, 0x4F, + 0x10, 0x00, 0x00, 0x00, 0x6A, 0x61, 0x6D, 0x65, 0x73, 0x7A, 0x61, 0x68, 0x61, 0x72, 0x79, 0x20, + 0x76, 0x41, 0x31, 0x20, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69, +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// AviWriterTask runs on cpu 1 to write the avi file +// + +TaskHandle_t CameraTask, AviWriterTask, FtpTask; +SemaphoreHandle_t baton; +int counter = 0; + +void codeForAviWriterTask( void * parameter ) +{ + uint32_t ulNotifiedValue; + Serial.print("aviwriter, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + for (;;) { + ulNotifiedValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + while (ulNotifiedValue-- > 0) { + make_avi(); + count_avi++; + delay(1); + } + } +} + +#ifdef include_ftp +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// FtpTask runs on cpu 0 to respond to ftp +// +void codeForFtpTask( void * parameter ) +{ + uint32_t ulNotifiedValue; + Serial.print("ftp, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + for (;;) { + //ftpSrv.handleFTP(); + filemgr.handleClient(); + count_ftp++; + delay(500); + + } +} +#endif + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// CameraTask runs on cpu 1 to take pictures and drop them in a queue +// + +void codeForCameraTask( void * parameter ) +{ + int pic_delay = 0; + int next = 0; + long next_run_time = 0; + Serial.print("camera, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + for (;;) { + + if (other_cpu_active == 1 ) { + current_millis = millis(); + count_cam++; + xSemaphoreTake( baton, portMAX_DELAY ); + + int q_size = (fb_in + fb_max - fb_out) % fb_max ; + + if ( q_size + 1 == fb_max) { + xSemaphoreGive( baton ); + + Serial.print(" Queue Full, Skipping ... "); // the queue is full + skipped++; skipped++; + skipping = 1; + next = 3 * capture_interval; + + } else { + frames_so_far++; + frame_cnt++; + + fb_in = (fb_in + 1) % fb_max; + bp = millis(); + + //fb_q[fb_in] = esp_camera_fb_get(); + //Serial.print (fb_q[fb_out]->buf[fblen-2],HEX ); Serial.print(":"); + //Serial.print (fb_q[fb_out]->buf[fblen-1],HEX ); //Serial.print(":"); + + do { + fb_q[fb_in] = esp_camera_fb_get(); + int x = fb_q[fb_in]->len; + int foundffd9 = 0; + //if (fb_q[fb_in]->buf[x - 1] != 0xD9) { + + for (int j = 1; j <= 1025; j++) { + if (fb_q[fb_in]->buf[x - j] != 0xD9) { + // no d9, try next for + } else { + + //Serial.println("Found a D9"); + if (fb_q[fb_in]->buf[x - j - 1] == 0xFF ) { + //Serial.print("Found the FFD9, junk is "); Serial.println(j); + if (j == 1) { + normal_jpg++; + } else { + extend_jpg++; + } + if (j > 1000) { // never happens. but > 1 does, usually 400-500 + Serial.print("Frame "); Serial.print(frames_so_far); + Serial.print(", Len = "); Serial.print(x); + Serial.print(", Corrent Len = "); Serial.print(x - j + 1); + Serial.print(", Extra Bytes = "); Serial.println( j - 1); + } + foundffd9 = 1; + break; + } + } + } + + if (!foundffd9) { + bad_jpg++; + Serial.print("Bad jpeg, Len = "); Serial.println(x); + esp_camera_fb_return(fb_q[fb_in]); + + } else { + break; + // count up the useless bytes + } + + } while (1); + + totalp = totalp - bp + millis(); + pic_delay = millis() - current_millis; + xSemaphoreGive( baton ); + last_capture_millis = millis(); + + if (q_size == 0) { + if (skipping == 1) { + Serial.println(" Queue cleared. "); + skipping = 0; + } + next = capture_interval - pic_delay; + if (next < 2) next = 2; + } else if (q_size < 2 ) { + next = capture_interval - pic_delay; + if (next < 2) next = 2; + } else if (q_size < 4 ) { + next = capture_interval ; + } else { + next = 2 * capture_interval; + skipped++; + //Serial.print(((fb_in + fb_max - fb_out) % fb_max)); + } + } + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(AviWriterTask, &xHigherPriorityTaskWoken); + + delay(next); + next_run_time = millis() + next; + } else { + next_run_time = millis() + capture_interval; + delay(capture_interval); + } + } + //delay(1); +} + + +// +// Writes an uint32_t in Big Endian at current file position +// +static void inline print_quartet(unsigned long i, FILE * fd) +{ + uint8_t y[4]; + //uint8_t x[1]; + + y[0] = i % 0x100; + y[1] = (i >> 8) % 0x100; + y[2] = (i >> 16) % 0x100; + y[3] = (i >> 24) % 0x100; + + size_t i1_err = fwrite(y , 1, 4, fd); + + //x[0] = i % 0x100; + //size_t i1_err = fwrite(x , 1, 1, fd); + //i = i >> 8; x[0] = i % 0x100; + //size_t i2_err = fwrite(x , 1, 1, fd); + //i = i >> 8; x[0] = i % 0x100; + //size_t i3_err = fwrite(x , 1, 1, fd); + //i = i >> 8; x[0] = i % 0x100; + //size_t i4_err = fwrite(x , 1, 1, fd); +} + + +void startCameraServer(); +httpd_handle_t camera_httpd = NULL; + +char the_page[4000]; + +char localip[20]; +WiFiEventId_t eventID; + +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" +#include "driver/rtc_io.h" + +#ifdef include_pir_and_touch + +//long TouchDeBounce = 0; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// PIR_ISR - interupt handler for PIR - starts or extends a video +// +static void IRAM_ATTR PIR_ISR(void* arg) { + + PIRstatus = digitalRead(PIRpin) + digitalRead(PIRpin) + digitalRead(PIRpin) ; + //Serial.print("PIR Interupt>> "); Serial.println(PIRstatus); + + //do_blink_short(); + if (PIRenabled == 1) { + if (PIRstatus == 3) { + if (PIRrecording == 1) { + // keep recording for 15 more seconds + + if ( (millis() - startms) > (total_frames * capture_interval - 5000) ) { + + total_frames = total_frames + 10000 / capture_interval ; + //Serial.print("PIR frames = "); Serial.println(total_frames); + Serial.print("#"); + //Serial.println("Add another 10 seconds"); + } + + } else { + + if ( recording == 0 && newfile == 0) { + + //start a pir recording with current parameters, except no repeat and 15 seconds + Serial.println("Start a PIR"); + PIRrecording = 1; + repeat = 0; + total_frames = 15000 / capture_interval; + startms = millis(); + Serial.print("PIR frames = "); Serial.println(total_frames); + xlength = total_frames * capture_interval / 1000; + recording = 1; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(AviWriterTask, &xHigherPriorityTaskWoken); + do_blink(); + } + } + } + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// setup some interupts during reboot +// + +static void setupinterrupts() { + + pinMode(PIRpin, INPUT_PULLDOWN); + + Serial.print("PIRpin = "); + for (int i = 0; i < 5; i++) { + Serial.print( digitalRead(PIRpin) ); Serial.print(", "); + } + Serial.println(" "); + + esp_err_t err = gpio_isr_handler_add((gpio_num_t)PIRpin, &PIR_ISR, NULL); + + if (err != ESP_OK) Serial.printf("gpio_isr_handler_add failed (%x)", err); + gpio_set_intr_type((gpio_num_t)PIRpin, GPIO_INTR_ANYEDGE); + + Serial.println(" "); +} + +#endif + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// blink functions - which turn on/off Blinding Disk-Active Light ... gently +// + +hw_timer_t * timer = NULL; + +// shut off the Blinding Disk-Active Light +void IRAM_ATTR onTimer() { + ledcWrite( 5, 0); +} + +// blink on the Blinding Disk-Active Light for 100 ms, 1/256th intensity +void do_blink() { + //Serial.println("<<<*** BLINK ***>>>"); + // timer 3, 80 million / 80000 = 1 millisecond, 100 ms + timer = timerBegin(3, 8000, true); + timerAttachInterrupt(timer, &onTimer, true); + timerAlarmWrite(timer, 100, false); + timerAlarmEnable(timer); + + // pwm channel 5, 5000 freq, 8 bit resolution, dutycycle 7, gpio 4 + + ledcSetup(5, 5000, 8 ); + ledcAttachPin(4, 5); + ledcWrite( 5, 7); +} + +void do_blink_short() { + //Serial.println("<<<*** blink ***>>>"); + // timer 3, 80 million / 80000 = 1 millisecond, 20 ms + timer = timerBegin(3, 8000, true); + timerAttachInterrupt(timer, &onTimer, true); + timerAlarmWrite(timer, 20, false); + timerAlarmEnable(timer); + + // pwm channel 5, 5000 freq, 8 bit resolution, dutycycle 1, gpio 4 + + ledcSetup(5, 5000, 8 ); + ledcAttachPin(4, 5); + ledcWrite( 5, 1); +} + +#ifdef include_telegram + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// save photos and send to telegram stuff +// + +uint8_t* fb_buffer; +size_t fb_length; +int currentByte; + +bool isMoreDataAvailable() { + return (fb_length - currentByte); +} + +uint8_t getNextByte() { + currentByte++; + return (fb_buffer[currentByte - 1]); +} + +void Send_text_telegram() { + + time(&now); + const char *strdate = ctime(&now); + + int tot = SD_MMC.totalBytes() / (1024 * 1024); + int use = SD_MMC.usedBytes() / (1024 * 1024); + long rssi = WiFi.RSSI(); + + const char msg[] PROGMEM = R"rawliteral( + ESP32-CAM Video Recorder %s + %s %s + Used %d MB / %d MB, Rssi %d + + %s + )rawliteral"; + + sprintf(the_page, msg, vernum, devname, localip, use, tot, rssi, fname); + + if (EnableBOT) bot.sendMessage(BOTme, the_page, ""); // "MarkdownV2"); +} + +#endif + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// print_ram - debugging function for show heap total and in tasks, loops through priority tasks +// + +void print_ram() { + Serial.println("cam / avi / ftp / ftp2 / loop "); + Serial.print(count_cam); Serial.print(" / "); + Serial.print(count_avi); Serial.print(" / "); + Serial.print(count_ftp); Serial.print(" / "); + Serial.print(count_ftp2); Serial.print(" / "); + Serial.print(count_loop); Serial.println(" "); + + Serial.printf("Internal Total heap %d, internal Free Heap %d\n", ESP.getHeapSize(), ESP.getFreeHeap()); + Serial.printf("SPIRam Total heap %d, SPIRam Free Heap %d\n", ESP.getPsramSize(), ESP.getFreePsram()); + + Serial.printf("ChipRevision %d, Cpu Freq %d, SDK Version %s\n", ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion()); + //Serial.printf(" Flash Size %d, Flash Speed %d\n",ESP.getFlashChipSize(), ESP.getFlashChipSpeed()); + + if (ready) { + Serial.println("Avi Writer / Camera / Ftp "); + Serial.print (uxTaskGetStackHighWaterMark(AviWriterTask)); + Serial.print (" / "); Serial.print (uxTaskGetStackHighWaterMark(CameraTask)); + Serial.print (" / "); Serial.println(uxTaskGetStackHighWaterMark(FtpTask)); + } + + + //Serial.printf( "Task Name\tStatus\tPrio\tHWM\tTask\tAffinity\n"); + // char stats_buffer[1024]; + //vTaskList(stats_buffer); + // vTaskGetRunTimeStats(stats_buffer); + // Serial.printf("%s\n\n", stats_buffer); + Serial.println("----"); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// save_photo_dated - just save one picture as a jpg and optioning send to telegram +// + +static void save_photo_dated() +{ + + Serial.println("Taking a picture for file ..."); + camera_fb_t *fb = esp_camera_fb_get(); + + time(&now); + localtime_r(&now, &timeinfo); + + //delay(2000); + + strftime(strftime_buf2, sizeof(strftime_buf2), "/%Y%m%d", &timeinfo); + SD_MMC.mkdir(strftime_buf2); + + strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); + + char jfname[130]; + + //v99 - uxga 13, hd 11, svga 9, vga 8, cif 6 + if (framesize == 8) { + sprintf(jfname, "/sdcard%s/%s_%s_vga_Q%d_I%d_L%d_S%d.jpg", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 9) { + sprintf(jfname, "/sdcard%s/%s_%s_svga_Q%d_I%d_L%d_S%d.jpg", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 11) { + sprintf(jfname, "/sdcard%s/%s_%s_hd_Q%d_I%d_L%d_S%d.jpg", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 13) { + sprintf(jfname, "/sdcard%s/%s_%s_uxga_Q%d_I%d_L%d_S%d.jpg", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 6) { + sprintf(jfname, "/sdcard%s/%s_%s_cif_Q%d_I%d_L%d_S%d.jpg", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else { + Serial.println("Wrong framesize"); + } + + FILE *file = fopen(jfname, "w"); + //file = fopen(fname, "w"); + if (file != NULL) { + size_t err = fwrite(fb->buf, 1, fb->len, file); + Serial.printf("File saved: %s\n", jfname); + } else { + Serial.printf("Could not open file: %s\n\n ", jfname); + } + fclose(file); + +#ifdef include_telegram + + if (EnableBOT == 1 && Internet_Enabled == 1) { + time(&now); + const char *strdate = ctime(&now); + + int tot = SD_MMC.totalBytes() / (1024 * 1024); + int use = SD_MMC.usedBytes() / (1024 * 1024); + long rssi = WiFi.RSSI(); + + const char msg[] PROGMEM = R"rawliteral( + ESP32-CAM Video Recorder %s + %s %s + Used %d MB / %d MB, Rssi %d + %s + )rawliteral"; + + const char msg2[] PROGMEM = R"rawliteral( + ESP32-CAM Video Recorder %s + %s %s + Used %d MB / %d MB, Rssi %d + %s + Previous Video + http://%s:8080/c?dwn=%s + )rawliteral"; + + // http://192.168.1.82:8080/c?dwn=/desklens42.007.avi + // the 7 below cuts off the prefix /sdcard + + if (strlen(fname) > 20) { + sprintf(the_page, msg2, vernum, devname, localip, use, tot, rssi, strdate, localip, &fname[7]); + } else { + sprintf(the_page, msg, vernum, devname, localip, use, tot, rssi, strdate ); + } + Serial.println("Taking a picture for telegram..."); + //camera_fb_t *fb = esp_camera_fb_get(); + + currentByte = 0; + fb_length = fb->len; + fb_buffer = fb->buf; + + Serial.print("Sending Photo Telegram, bytes: "); Serial.println(fb_length); + + Serial.print("\nSend_photo heap before: "); Serial.println(ESP.getFreeHeap()); + + String sent = bot.sendMultipartFormDataToTelegramWithCaption("sendPhoto", "photo", "img.jpg", + "image/jpeg", the_page, BOTme, fb_length, + isMoreDataAvailable, getNextByte, nullptr, nullptr); + + Serial.print("\nSend_photo heap after : "); Serial.println(ESP.getFreeHeap()); + + if (sent.length() > 1) { + Serial.println("\nPhoto telegram was successfully sent "); // Serial.print(sent); Serial.println("<"); + + } else { + Serial.print("\nPhoto telegram failed >"); + Serial.print(sent); Serial.println("<"); + } + + } +#endif + + esp_camera_fb_return(fb); +} + + +#ifdef include_streaming + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Streaming stuff from Random Nerd +// https://randomnerdtutorials.com/esp32-cam-video-streaming-web-server-camera-home-assistant/ +// + +#define PART_BOUNDARY "123456789000000000000987654321" + +static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; +static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; +static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n"; + +//httpd_handle_t stream_httpd = NULL; + +static esp_err_t stream_handler(httpd_req_t *req) { + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + size_t _jpg_buf_len = 0; + uint8_t * _jpg_buf = NULL; + char * part_buf[64]; + + Serial.print("stream_handler, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); + if (res != ESP_OK) { + return res; + } + + while (true) { + //xSemaphoreTake( baton, portMAX_DELAY ); + + fb = esp_camera_fb_get(); + if (!fb) { + Serial.println("Camera capture failed"); + res = ESP_FAIL; + } else { + + _jpg_buf_len = fb->len; + _jpg_buf = fb->buf; + + } + if (res == ESP_OK) { + size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len); + res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen); + } + if (res == ESP_OK) { + res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len); + } + if (res == ESP_OK) { + res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); + } + const char *strdate = ctime(&now); + + if (fb) { + + esp_camera_fb_return(fb); + //xSemaphoreGive( baton ); + + fb = NULL; + _jpg_buf = NULL; + } else if (_jpg_buf) { + free(_jpg_buf); + _jpg_buf = NULL; + } + if (res != ESP_OK) { + break; + } + //Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len)); + //Serial.print("s"); + delay(stream_interval); + } + return res; +} +#endif + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// delete_old_stuff() - before every avi, delete oldest day if freespace less than 10% +// +// code copied from user @gemi254 + +void delete_old_stuff() { + + Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); + + float full = 1.0 * SD_MMC.usedBytes() / SD_MMC.totalBytes();; + if (full < 0.9) { + Serial.printf("Nothing deleted, %.1f%% disk full\n", 100.0 * full); + } else { + Serial.printf("Disk is %.1f%% full ... deleting oldest day\n", 100.0 * full); + int oldest = 22222222; + char oldestname[50]; + + File f = SD_MMC.open("/"); + if (f.isDirectory()) { + + File file = f.openNextFile(); + while (file) { + if (file.isDirectory()) { + + char foldname[50]; + strcpy(foldname, file.name()); + foldname[0] = 0x20; + int i = atoi(foldname); + if (i > 20200000 && i < oldest) { + strcpy (oldestname, file.name()); + oldest = i; + } + //Serial.printf("Name is %s, number is %d\n", foldname, i); + } + file = f.openNextFile(); + } + //Serial.printf("Oldest is Name is %s, number is %d\n", oldestname, oldest); + deleteFolderOrFile(oldestname); + f.close(); + } + } +} + +void deleteFolderOrFile(const char * val) { + // Function provided by user @gemi254 + Serial.printf("Deleting : %s\n", val); + File f = SD_MMC.open(val); + if (!f) { + Serial.printf("Failed to open %s\n", val); + return; + } + + if (f.isDirectory()) { + File file = f.openNextFile(); + while (file) { + if (file.isDirectory()) { + Serial.print(" DIR : "); + Serial.println(file.name()); + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.print(file.size()); + if (SD_MMC.remove(file.name())) { + Serial.println(" deleted."); + } else { + Serial.println(" FAILED."); + } + } + file = f.openNextFile(); + } + f.close(); + //Remove the dir + if (SD_MMC.rmdir(val)) { + Serial.printf("Dir %s removed\n", val); + } else { + Serial.println("Remove dir failed"); + } + + } else { + //Remove the file + if (SD_MMC.remove(val)) { + Serial.printf("File %s deleted\n", val); + } else { + Serial.println("Delete failed"); + } + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// setup() runs on cpu 1 +// + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "soc/soc.h" +#include "soc/cpu.h" +#include "soc/rtc_cntl_reg.h" + +#include "esp_task_wdt.h" + +#ifdef CONFIG_BROWNOUT_DET_LVL +#define BROWNOUT_DET_LVL CONFIG_BROWNOUT_DET_LVL +#else +#define BROWNOUT_DET_LVL 5 +#endif //CONFIG_BROWNOUT_DET_LVL + +#define CONFIG_BROWNOUT_DET_LVL_SEL_5 1 + +#ifdef include_pir_and_touch + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// print_wakeup_reason - display message after deepsleep wakeup +// + +RTC_DATA_ATTR int bootCount = 0; + +void print_wakeup_reason() { + esp_sleep_wakeup_cause_t wakeup_reason; + + wakeup_reason = esp_sleep_get_wakeup_cause(); + + switch (wakeup_reason) { + case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break; + case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; + case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break; + case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break; + case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break; + default : Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break; + } +} +#endif + +void do_eprom_read() { + + eprom_data ed; + + long x = millis(); + EEPROM.begin(200); + EEPROM.get(0, ed); + Serial.println("Get took " + String(millis() - x)); + + if (ed.eprom_good == MagicNumber) { + Serial.println("Good settings in the EPROM "); + + Internet_Enabled = ed.Internet_Enabled; Serial.print("Internet_Enabled "); Serial.println(Internet_Enabled ); + DeepSleepPir = ed.DeepSleepPir; Serial.print("DeepSleepPir "); Serial.println(DeepSleepPir ); + record_on_reboot = ed.record_on_reboot; Serial.print("record_on_reboot "); Serial.println(record_on_reboot ); + PIRpin = ed.PIRpin; Serial.print("PIRpin "); Serial.println(PIRpin ); + PIRenabled = ed.PIRenabled; Serial.print("PIRenabled "); Serial.println(PIRenabled ); + framesize = ed.framesize; Serial.print("framesize "); Serial.println(framesize ); + repeat_config = ed.repeat; Serial.print("repeat_config "); Serial.println(repeat_config ); + repeat = ed.repeat; + xspeed = ed.xspeed; Serial.print("xspeed "); Serial.println(xspeed ); + gray = ed.gray; Serial.print("gray "); Serial.println(gray ); + quality = ed.quality; Serial.print("quality "); Serial.println(quality ); + capture_interval = ed.capture_interval; Serial.print("capture_interval "); Serial.println(capture_interval ); + total_frames = ed.total_frames; + total_frames_config = ed.total_frames; Serial.print("total_frames_config "); Serial.println(total_frames_config ); + xlength = ed.xlength; Serial.print("xlength "); Serial.println(xlength ); + EnableBOT = ed.EnableBOT; Serial.print("EnableBOT "); Serial.println(EnableBOT ); + } else { + Serial.println("No settings in EPROM - putting in hardcoded settings "); + do_eprom_write(); + } +} + + +void do_eprom_write() { + + eprom_data ed; + + Serial.println("Write settings in the EPROM "); + ed.eprom_good = MagicNumber; + ed.Internet_Enabled = Internet_Enabled; + ed.DeepSleepPir = DeepSleepPir; + ed.record_on_reboot = record_on_reboot; + ed.PIRpin = PIRpin; + ed.PIRenabled = PIRenabled; + ed.framesize = framesize; + ed.repeat = repeat_config; + ed.xspeed = xspeed; + ed.gray = gray; + ed.quality = quality; + ed.capture_interval = capture_interval; + ed.total_frames = total_frames_config; + ed.xlength = xlength; + ed.EnableBOT = EnableBOT; + + Serial.println("Writing to EPROM ..."); + + long x = millis(); + EEPROM.begin(200); + EEPROM.put(0, ed); + EEPROM.commit(); + EEPROM.end(); + + Serial.println("Put took " + String(millis() - x) + " ms, bytes = " + String(sizeof(ed))); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// setup() - the Arduino setup +// + +void setup() { + + Serial.begin(115200); + Serial.println("\n\n---"); + + rtc_gpio_hold_dis(GPIO_NUM_33); + pinMode(33, OUTPUT); // little red led on back of chip + digitalWrite(33, LOW); // turn on the red LED on the back of chip + + rtc_gpio_hold_dis(GPIO_NUM_4); + pinMode(4, OUTPUT); // Blinding Disk-Avtive Light + digitalWrite(4, LOW); // turn off + + Serial.setDebugOutput(true); + Serial.print("setup, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + // zzz + Serial.println(" "); + Serial.println("-------------------------------------"); + Serial.printf("ESP-CAM Video Recorder %s\n", vernum); + Serial.printf(" http://%s.local - to access the camera\n", devname); + Serial.println("-------------------------------------"); + +#ifdef include_pir_and_touch + ++bootCount; + Serial.println("Boot number: " + String(bootCount)); + print_wakeup_reason(); +#endif + + do_eprom_read(); + repeat = repeat_config; + total_frames = total_frames_config; + + if (!psramFound()) { + Serial.println("paraFound wrong - major fail"); + major_fail(); + } + + if (Internet_Enabled) { + Serial.println("Starting wifi ..."); + if (init_wifi()) { // Connected to WiFi + internet_connected = true; + } else { + Serial.println("Internet skipped"); + internet_connected = false; + } + } + //plm print_ram(); delay(1000); + Serial.println("Starting sd card ..."); + + // SD camera init + card_err = init_sdcard(); + if (card_err != ESP_OK) { + Serial.printf("SD Card init failed with error 0x%x", card_err); + major_fail(); + return; + } + + //plm print_ram(); delay(2000); + Serial.println("Starting server ..."); + + if (Internet_Enabled && !InternetFailed) startCameraServer(); + + // zzz username and password for ftp server + +#ifdef include_ftp + //plm print_ram(); delay(2000); + Serial.println("Starting ftp ..."); + + if (Internet_Enabled && !InternetFailed) { + Serial.println("Starting the esp32 sd file manager"); + filemgr.begin(); + //filemgr.handleClient(); //ftpSrv.begin("esp", "esp"); + } +#endif + + Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); + + //plm print_ram(); delay(2000); + Serial.println("Starting tasks ..."); + + baton = xSemaphoreCreateMutex(); // baton controls access to camera and frame queue + + xTaskCreatePinnedToCore( + codeForCameraTask, + "CameraTask", + 1024, // heap available for this task + NULL, + 2, // prio 2 - higher number is higher priio + &CameraTask, + 1); // core 1 + + delay(20); + + xTaskCreatePinnedToCore( + codeForAviWriterTask, + "AviWriterTask", + 3072, // heap + NULL, + 3, // prio 3 + &AviWriterTask, + 1); // on cpu 1 - same as ftp http + + delay(20); + +#ifdef include_ftp + if (Internet_Enabled && !InternetFailed) { + xTaskCreatePinnedToCore( + codeForFtpTask, + "FtpTask", + 4096, // heap + NULL, + 4, // prio higher than 1 + &FtpTask, + 1); // on cpu 0 // switch to 1 so both cores hashare the http traffic // a1 + + delay(20); + } +#endif + + //plm print_ram(); delay(2000); + Serial.println("Starting camera ..."); + + recording = 0; // we are NOT recording + config_camera(); + +#ifdef include_pir_and_touch + setupinterrupts(); +#endif + + newfile = 0; // no file is open // don't fiddle with this! + + recording = record_on_reboot; + + //plm print_ram(); delay(2000); + + ready = 1; + digitalWrite(33, HIGH); // red light turns off when setup is complete + + Serial.print("Camera Ready! Use 'http://"); + Serial.print(WiFi.localIP()); + Serial.println("' to connect"); + + xTaskNotifyGive(AviWriterTask); + + delay(1000); + + print_ram(); + + if (delete_old_files) delete_old_stuff(); +} + + +// +// if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS +// +void major_fail() { + + Serial.println(" "); + + for (int i = 0; i < 10; i++) { // 10 loops or about 100 seconds then reboot + for (int j = 0; j < 3; j++) { + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + } + + delay(1000); + + for (int j = 0; j < 3; j++) { + digitalWrite(33, LOW); delay(500); + digitalWrite(33, HIGH); delay(500); + } + + delay(1000); + Serial.print("Major Fail "); Serial.print(i); Serial.print(" / "); Serial.println(10); + } + + ESP.restart(); +} + + +bool init_wifi() +{ + int connAttempts = 0; + + Serial.println(" Disable brownout"); + uint32_t brown_reg_temp = READ_PERI_REG(RTC_CNTL_BROWN_OUT_REG); //save WatchDog register + Serial.print("\nBrownOut Regsiter was (in hex) "); Serial.println(brown_reg_temp, HEX); + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector + + if (strlen(ssid) == 1) { + //if (1) { + WiFiManager wm; + bool res; + //wm.resetSettings(); // for debugging + + wm.setConnectTimeout(20); // how long to try to connect for before continuing + wm.setConfigPortalTimeout(30); // auto close configportal after n seconds + + // res = wm.autoConnect(); // auto generated AP name from chipid + + res = wm.autoConnect(devname); // use the devname defined above, with no password + //res = wm.autoConnect("AutoConnectAP","password"); // password protected ap + + if (res) { + Serial.println("Succesful Connection using WiFiManager"); + + } else { + + InternetFailed = true; + Serial.println("Internet failed using WiFiManager - not starting Web services"); + } + } else { + + WiFi.disconnect(true, true); + WiFi.mode(WIFI_STA); + WiFi.setHostname(devname); + //WiFi.printDiag(Serial); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED ) { + delay(1000); + Serial.print("."); + if (connAttempts == 20 ) { + Serial.println("Cannot connect - try again"); + WiFi.begin(ssid, password); + } + if (connAttempts == 30) { + Serial.println("Cannot connect - fail"); + + WiFi.printDiag(Serial); + return false; + } + connAttempts++; + } + + Serial.println("\nInternet connected"); + } + + if (!InternetFailed) { + if (!MDNS.begin(devname)) { + Serial.println("Error setting up MDNS responder!"); + } else { + Serial.printf("mDNS responder started '%s'\n", devname); + } + + configTime(0, 0, "pool.ntp.org"); + + setenv("TZ", TIMEZONE, 1); // mountain time zone from #define at top + tzset(); + + time_t now ; + timeinfo = { 0 }; + int retry = 0; + const int retry_count = 15; + delay(1000); + time(&now); + localtime_r(&now, &timeinfo); + + while (timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) { + Serial.printf("Waiting for system time to be set... (%d/%d) -- %d\n", retry, retry_count, timeinfo.tm_year); + delay(1000); + time(&now); + localtime_r(&now, &timeinfo); + } + + Serial.print("Local time: "); Serial.println(ctime(&now)); + sprintf(localip, "%s", WiFi.localIP().toString().c_str()); + } + + //typedef enum { + // WIFI_PS_NONE, /**< No power save */ + // WIFI_PS_MIN_MODEM, /**< Minimum modem power saving. In this mode, station wakes up to receive beacon every DTIM period */ + // WIFI_PS_MAX_MODEM, /**< Maximum modem power saving. In this mode, interval to receive beacons is determined by the listen_interval + // parameter in wifi_sta_config_t. + // Attention: Using this option may cause ping failures. Not recommended */ + //} wifi_ps_type_t; + + wifi_ps_type_t the_type; + + esp_err_t get_ps = esp_wifi_get_ps(&the_type); + Serial.printf("The power save was: %d\n", the_type); + + Serial.printf("Set power save to %d\n", WIFI_PS_NONE); + esp_err_t set_ps = esp_wifi_set_ps(WIFI_PS_NONE); + + esp_err_t new_ps = esp_wifi_get_ps(&the_type); + Serial.printf("The power save is : %d\n", the_type); + + Serial.println(" Enable brownout"); + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, brown_reg_temp); //enable brownout detector + + // v99 for esp32-1.06 https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/issues/235 + + client.setInsecure(); + + return true; +} + + +static esp_err_t init_sdcard() +{ + + //pinMode(12, PULLUP); + pinMode(13, PULLUP); + //pinMode(4, OUTPUT); + + esp_err_t ret = ESP_FAIL; + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.flags = SDMMC_HOST_FLAG_1BIT; // using 1 bit mode + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + diskspeed = host.max_freq_khz; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width = 1; // using 1 bit mode + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 8, + }; + + sdmmc_card_t *card; + + ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); + + if (ret == ESP_OK) { + Serial.println("SD card mount successfully!"); + } else { + Serial.printf("Failed to mount SD card VFAT filesystem. Error: %s", esp_err_to_name(ret)); + Serial.println("Try again..."); + delay(5000); + diskspeed = 400; + host.max_freq_khz = SDMMC_FREQ_PROBING; + ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); + if (ret == ESP_OK) { + Serial.println("SD card mount successfully SLOW SLOW SLOW"); + } else { + Serial.printf("Failed to mount SD card VFAT filesystem. Error: %s", esp_err_to_name(ret)); + major_fail(); + } + } + sdmmc_card_print_info(stdout, card); + Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? + return ret; + //pinMode(13, PULLDOWN); + //pinMode(13, INPUT_PULLDOWN); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Make the avi move in 4 pieces +// +// make_avi() called in every loop, which calls below, depending on conditions +// start_avi() - open the file and write headers +// another_pic_avi() - write one more frame of movie +// end_avi() - write the final parameters and close the file + +void make_avi( ) { + +#ifdef include_pir_and_touch + + if (PIRenabled == 1) { + PIRstatus = digitalRead(PIRpin) + digitalRead(PIRpin) + digitalRead(PIRpin) ; + //Serial.println(millis()); + if (DeepSleepPir == 1 && millis() < 15000 ) { + //DeepSleepPir = 0; + PIRstatus = 3; + } + //Serial.print("Mak>> "); Serial.println(PIRstatus); + if (PIRstatus == 3) { + + if (PIRrecording == 1) { + // keep recording for 15 more seconds + if ( (millis() - startms) > (total_frames * capture_interval - 5000) ) { + + total_frames = total_frames + 10000 / capture_interval ; + //Serial.print("Make PIR frames = "); Serial.println(total_frames); + Serial.print("@"); + //Serial.println("Add another 10 seconds"); + } + + } else { + + if ( recording == 0 && newfile == 0) { + + //start a pir recording with current parameters, except no repeat and 15 seconds + Serial.println("Start a PIR"); + PIRrecording = 1; + repeat = 0; + total_frames = 15000 / capture_interval; + xlength = total_frames * capture_interval / 1000; + recording = 1; + } + } + } + } + +#endif + + // we are recording, but no file is open + + if (newfile == 0 && recording == 1) { // open the file + + digitalWrite(33, HIGH); + newfile = 1; + + if (EnableBOT == 1 && Internet_Enabled == 1) { // if BOT is enabled wait to send it ... could be several seconds (5 or 10) + //89 config_camera(); + send_a_telegram = 1; + Wait_for_bot = 1; + + while (Wait_for_bot == 1) { + delay(1000); + Serial.print("z"); // serial monitor will shows these "z" mixed with "*" from telegram sender + } + } + Serial.println(" "); + + if (delete_old_files) delete_old_stuff(); + + start_avi(); // now start the avi + + } else { + + // we have a file open, but not recording + + if (newfile == 1 && recording == 0) { // got command to close file + + digitalWrite(33, LOW); + end_avi(); + + Serial.println("Done capture due to command"); + + frames_so_far = total_frames; + + newfile = 0; // file is closed + recording = 0; // DO NOT start another recording + PIRrecording = 0; + + } else { + + if (newfile == 1 && recording == 1) { // regular recording + + if ((millis() - startms) > (total_frames * capture_interval)) { // time is up, even though we have not done all the frames + + Serial.println (" "); Serial.println("Done capture for time"); + Serial.print("Time Elapsed: "); Serial.print(millis() - startms); Serial.print(" Frames: "); Serial.println(frame_cnt); + Serial.print("Config: "); Serial.print(total_frames * capture_interval ) ; Serial.print(" ("); + Serial.print(total_frames); Serial.print(" x "); Serial.print(capture_interval); Serial.println(")"); + + digitalWrite(33, LOW); // close the file + + end_avi(); + + frames_so_far = 0; + newfile = 0; // file is closed + if (repeat > 0) { + recording = 1; // start another recording + repeat = repeat - 1; + xTaskNotifyGive(AviWriterTask); + } else { + recording = 0; + PIRrecording = 0; + } + + } else { // regular + + another_save_avi(); + + } + } + } + } +} + +static void config_camera() { + + camera_config_t config; + + //Serial.println("config camera"); + + if (new_config == 5) { + + 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_UXGA; + + // v99 - lets get rid of this queuing system --- not just yet + // fb_max = 6; //74.5 from 7 // for vga and uxga + // config.jpeg_quality = 6; //74.5 from 7 + + fb_max = 6; //74.5 from 7 // for vga and uxga + config.jpeg_quality = 6; //74.5 from 7 + + config.fb_count = fb_max + 1; + + // camera init + cam_err = esp_camera_init(&config); + if (cam_err != ESP_OK) { + Serial.printf("Camera init failed with error 0x%x", cam_err); + major_fail(); + } + + new_config = 2; + } + + delay(100); + + sensor_t * ss = esp_camera_sensor_get(); + ss->set_quality(ss, quality); + ss->set_framesize(ss, (framesize_t)framesize); + if (gray == 1) { + ss->set_special_effect(ss, 2); // 0 regular, 2 grayscale + } else { + ss->set_special_effect(ss, 0); // 0 regular, 2 grayscale + } + ss->set_brightness(ss, 1); //up the blightness just a bit + ss->set_saturation(ss, -2); //lower the saturation + + + for (int j = 0; j < 5; j++) { + do_fb(); // start the camera ... warm it up + delay(50); + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// start_avi - open the files and write in headers +// + + +static void start_avi() { + + Serial.println("Starting an avi "); + + //plm print_ram(); + + //89 config_camera(); + + time(&now); + localtime_r(&now, &timeinfo); + + strftime(strftime_buf2, sizeof(strftime_buf2), "/%Y%m%d", &timeinfo); + SD_MMC.mkdir(strftime_buf2); + + strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); + + //v99 - uxga 13, hd 11, svga 9, vga 8, cif 6 + if (framesize == 8) { + sprintf(fname, "/sdcard%s/%s_%s_vga_Q%d_I%d_L%d_S%d.avi", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 9) { + sprintf(fname, "/sdcard%s/%s_%s_svga_Q%d_I%d_L%d_S%d.avi", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 11) { + sprintf(fname, "/sdcard%s/%s_%s_hd_Q%d_I%d_L%d_S%d.avi", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 13) { + sprintf(fname, "/sdcard%s/%s_%s_uxga_Q%d_I%d_L%d_S%d.avi", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 6) { + sprintf(fname, "/sdcard%s/%s_%s_cif_Q%d_I%d_L%d_S%d.avi", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else { + Serial.println("Wrong framesize"); + } + + Serial.print("\nFile name will be >"); Serial.print(fname); Serial.println("<"); + + avifile = fopen(fname, "w"); + idxfile = fopen("/sdcard/idx.tmp", "w"); + + if (avifile != NULL) { + //Serial.printf("File open: %s\n", fname); + } else { + Serial.println("Could not open file"); + major_fail(); + } + + if (idxfile != NULL) { + //Serial.printf("File open: %s\n", "/sdcard/idx.tmp"); + } else { + Serial.println("Could not open file"); + major_fail(); + } + + for ( i = 0; i < AVIOFFSET; i++) + { + char ch = pgm_read_byte(&avi_header[i]); + buf[i] = ch; + } + + size_t err = fwrite(buf, 1, AVIOFFSET, avifile); + //v99 - uxga 13, hd 11, svga 9, vga 8, cif 6 + if (framesize == 8) { + + fseek(avifile, 0x40, SEEK_SET); + err = fwrite(vga_w, 1, 2, avifile); + fseek(avifile, 0xA8, SEEK_SET); + err = fwrite(vga_w, 1, 2, avifile); + fseek(avifile, 0x44, SEEK_SET); + err = fwrite(vga_h, 1, 2, avifile); + fseek(avifile, 0xAC, SEEK_SET); + err = fwrite(vga_h, 1, 2, avifile); + + } else if (framesize == 13) { + + fseek(avifile, 0x40, SEEK_SET); + err = fwrite(uxga_w, 1, 2, avifile); + fseek(avifile, 0xA8, SEEK_SET); + err = fwrite(uxga_w, 1, 2, avifile); + fseek(avifile, 0x44, SEEK_SET); + err = fwrite(uxga_h, 1, 2, avifile); + fseek(avifile, 0xAC, SEEK_SET); + err = fwrite(uxga_h, 1, 2, avifile); + + } else if (framesize == 11) { + + fseek(avifile, 0x40, SEEK_SET); + err = fwrite(hd_w, 1, 2, avifile); + fseek(avifile, 0xA8, SEEK_SET); + err = fwrite(hd_w, 1, 2, avifile); + fseek(avifile, 0x44, SEEK_SET); + err = fwrite(hd_h, 1, 2, avifile); + fseek(avifile, 0xAC, SEEK_SET); + err = fwrite(hd_h, 1, 2, avifile); + + + } else if (framesize == 9) { + + fseek(avifile, 0x40, SEEK_SET); + err = fwrite(svga_w, 1, 2, avifile); + fseek(avifile, 0xA8, SEEK_SET); + err = fwrite(svga_w, 1, 2, avifile); + fseek(avifile, 0x44, SEEK_SET); + err = fwrite(svga_h, 1, 2, avifile); + fseek(avifile, 0xAC, SEEK_SET); + err = fwrite(svga_h, 1, 2, avifile); + + } else if (framesize == 6) { + + fseek(avifile, 0x40, SEEK_SET); + err = fwrite(cif_w, 1, 2, avifile); + fseek(avifile, 0xA8, SEEK_SET); + err = fwrite(cif_w, 1, 2, avifile); + fseek(avifile, 0x44, SEEK_SET); + err = fwrite(cif_h, 1, 2, avifile); + fseek(avifile, 0xAC, SEEK_SET); + err = fwrite(cif_h, 1, 2, avifile); + } + + fseek(avifile, AVIOFFSET, SEEK_SET); + + Serial.print(F("\nRecording ")); + Serial.print(total_frames); + Serial.println(F(" video frames ...\n")); + + startms = millis(); + bigdelta = millis(); + totalp = 0; + totalw = 0; + overtime_count = 0; + jpeg_size = 0; + movi_size = 0; + uVideoLen = 0; + idx_offset = 4; + + + frame_cnt = 0; + frames_so_far = 0; + + skipping = 0; + skipped = 0; + bad_jpg = 0; + extend_jpg = 0; + normal_jpg = 0; + + newfile = 1; + other_cpu_active = 1; + + +} // end of start avi + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// another_save_avi runs on cpu 1, saves another frame to the avi file +// +// the "baton" semaphore makes sure that only one cpu is using the camera subsystem at a time +// + +static void another_save_avi() { + + xSemaphoreTake( baton, portMAX_DELAY ); + + if (fb_in == fb_out) { // nothing to do + + xSemaphoreGive( baton ); + nothing_avi++; + + } else { + + fb_out = (fb_out + 1) % fb_max; + + int fblen; + fblen = fb_q[fb_out]->len; + + //xSemaphoreGive( baton ); + + if (BlinkWithWrite) { + digitalWrite(33, LOW); + } + + jpeg_size = fblen; + movi_size += jpeg_size; + uVideoLen += jpeg_size; + + bw = millis(); + size_t dc_err = fwrite(dc_buf, 1, 4, avifile); + size_t ze_err = fwrite(zero_buf, 1, 4, avifile); + + //bw = millis(); + + int time_to_give_up = 0; + while (ESP.getFreeHeap() < 35000) { + Serial.print(time_to_give_up); Serial.print(" Low on heap "); Serial.print(ESP.getFreeHeap()); + Serial.print(" frame q = "); Serial.println((fb_in + fb_max - fb_out) % fb_max); + if (time_to_give_up++ == 50) break; + delay(100 + 5 * time_to_give_up); + } + + ///Serial.print(fblen); Serial.print(" "); + //Serial.print (fb_q[fb_out]->buf[fblen-3],HEX ); Serial.print(":"); + ///Serial.print (fb_q[fb_out]->buf[fblen-2],HEX ); Serial.print(":"); + ///Serial.print (fb_q[fb_out]->buf[fblen-1],HEX ); //Serial.print(":"); + //Serial.print (fb_q[fb_out]->buf[fblen ],HEX ); Serial.print(":"); + ///Serial.println(""); + + size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile); + + time_to_give_up = 0; + while (err != fb_q[fb_out]->len) { + Serial.print("Error on avi write: err = "); Serial.print(err); + Serial.print(" len = "); Serial.println(fb_q[fb_out]->len); + time_to_give_up++; + if (time_to_give_up == 10) major_fail(); + Serial.print(time_to_give_up); Serial.print(" Low on heap !!! "); Serial.println(ESP.getFreeHeap()); + + delay(1000); + size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile); + + } + + //totalw = totalw + millis() - bw; + + //xSemaphoreTake( baton, portMAX_DELAY ); + esp_camera_fb_return(fb_q[fb_out]); // release that buffer back to the camera system + xSemaphoreGive( baton ); + + remnant = (4 - (jpeg_size & 0x00000003)) & 0x00000003; + + print_quartet(idx_offset, idxfile); + print_quartet(jpeg_size, idxfile); + + idx_offset = idx_offset + jpeg_size + remnant + 8; + + jpeg_size = jpeg_size + remnant; + movi_size = movi_size + remnant; + if (remnant > 0) { + size_t rem_err = fwrite(zero_buf, 1, remnant, avifile); + } + + fileposition = ftell (avifile); // Here, we are at end of chunk (after padding) + fseek(avifile, fileposition - jpeg_size - 4, SEEK_SET); // Here we are the the 4-bytes blank placeholder + + print_quartet(jpeg_size, avifile); // Overwrite placeholder with actual frame size (without padding) + + fileposition = ftell (avifile); + + // ver97 This is not strictly necessay, so lets get rid of get + /// fseek(avifile, fileposition + 6, SEEK_SET); // Here is the FOURCC "JFIF" (JPEG header) - Overwrite "JFIF" (still images) with more appropriate "AVI1" + /// size_t av_err = fwrite(avi1_buf, 1, 4, avifile); + /// fileposition = ftell (avifile); + /// fseek(avifile, fileposition + jpeg_size - 10 , SEEK_SET); + + fseek(avifile, fileposition + jpeg_size , SEEK_SET); + + totalw = totalw + millis() - bw; + + digitalWrite(33, HIGH); + + } +} // end of another_pic_avi + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// end_avi runs on cpu 1, empties the queue of frames, writes the index, and closes the files +// + +static void end_avi() { + + unsigned long current_end = 0; + + other_cpu_active = 0 ; // shuts down the picture taking program + + //Serial.print(" Write Q: "); Serial.print((fb_in + fb_max - fb_out) % fb_max); Serial.print(" in/out "); Serial.print(fb_in); Serial.print(" / "); Serial.println(fb_out); + + for (int i = 0; i < fb_max; i++) { // clear the queue + another_save_avi(); + } + + //Serial.print(" Write Q: "); Serial.print((fb_in + fb_max - fb_out) % fb_max); Serial.print(" in/out "); Serial.print(fb_in); Serial.print(" / "); Serial.println(fb_out); + + current_end = ftell (avifile); + + Serial.println("End of avi - closing the files"); + + elapsedms = millis() - startms; + float fRealFPS = (1000.0f * (float)frame_cnt) / ((float)elapsedms) * xspeed; + float fmicroseconds_per_frame = 1000000.0f / fRealFPS; + uint8_t iAttainedFPS = round(fRealFPS); + uint32_t us_per_frame = round(fmicroseconds_per_frame); + + //Modify the MJPEG header from the beginning of the file, overwriting various placeholders + + fseek(avifile, 4 , SEEK_SET); + print_quartet(movi_size + 240 + 16 * frame_cnt + 8 * frame_cnt, avifile); + + fseek(avifile, 0x20 , SEEK_SET); + print_quartet(us_per_frame, avifile); + + unsigned long max_bytes_per_sec = movi_size * iAttainedFPS / frame_cnt; + + fseek(avifile, 0x24 , SEEK_SET); + print_quartet(max_bytes_per_sec, avifile); + + fseek(avifile, 0x30 , SEEK_SET); + print_quartet(frame_cnt, avifile); + + fseek(avifile, 0x8c , SEEK_SET); + print_quartet(frame_cnt, avifile); + + fseek(avifile, 0x84 , SEEK_SET); + print_quartet((int)iAttainedFPS, avifile); + + fseek(avifile, 0xe8 , SEEK_SET); + print_quartet(movi_size + frame_cnt * 8 + 4, avifile); + + Serial.println(F("\n*** Video recorded and saved ***\n")); + Serial.print(F("Recorded ")); + Serial.print(elapsedms / 1000); + Serial.print(F("s in ")); + Serial.print(frame_cnt); + Serial.print(F(" frames\nFile size is ")); + Serial.print(movi_size + 12 * frame_cnt + 4); + Serial.print(F(" bytes\nActual FPS is ")); + Serial.print(fRealFPS, 2); + Serial.print(F("\nMax data rate is ")); + Serial.print(max_bytes_per_sec); + Serial.print(F(" byte/s\nFrame duration is ")); Serial.print(us_per_frame); Serial.println(F(" us")); + Serial.print(F("Average frame length is ")); Serial.print(uVideoLen / frame_cnt); Serial.println(F(" bytes")); + Serial.print("Average picture time (ms) "); Serial.println( 1.0 * totalp / frame_cnt); + Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt ); + Serial.print("Frames Skipped % "); Serial.println( 100.0 * skipped / frame_cnt, 2 ); + Serial.print("Normal jpg % "); Serial.println( 100.0 * normal_jpg / frame_cnt, 1 ); + Serial.print("Extend jpg % "); Serial.println( 100.0 * extend_jpg / frame_cnt, 1 ); + Serial.print("Bad jpg % "); Serial.println( 100.0 * bad_jpg / total_frames, 1 ); + + Serial.println("Writing the index"); + + fseek(avifile, current_end, SEEK_SET); + + fclose(idxfile); + + size_t i1_err = fwrite(idx1_buf, 1, 4, avifile); + + print_quartet(frame_cnt * 16, avifile); + + idxfile = fopen("/sdcard/idx.tmp", "r"); + + if (idxfile != NULL) { + //Serial.printf("File open: %s\n", "/sdcard/idx.tmp"); + } else { + Serial.println("Could not open file"); + //major_fail(); + } + + char * AteBytes; + AteBytes = (char*) malloc (8); + + for (int i = 0; i < frame_cnt; i++) { + size_t res = fread ( AteBytes, 1, 8, idxfile); + size_t i1_err = fwrite(dc_buf, 1, 4, avifile); + size_t i2_err = fwrite(zero_buf, 1, 4, avifile); + size_t i3_err = fwrite(AteBytes, 1, 8, avifile); + } + + free(AteBytes); + fclose(idxfile); + fclose(avifile); + int xx = remove("/sdcard/idx.tmp"); + + Serial.println("---"); + +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// do_fb - just takes a picture and discards it +// + +static void do_fb() { + xSemaphoreTake( baton, portMAX_DELAY ); + camera_fb_t * fb = esp_camera_fb_get(); + + //Serial.print("Pic, len="); Serial.println(fb->len); + + esp_camera_fb_return(fb); + xSemaphoreGive( baton ); +} + +void do_time() { + + if (WiFi.status() != WL_CONNECTED) { + + Serial.println("***** WiFi reconnect *****"); + WiFi.reconnect(); + delay(5000); + + if (WiFi.status() != WL_CONNECTED) { + Serial.println("***** WiFi rerestart *****"); + init_wifi(); + } + + MDNS.begin(devname); + sprintf(localip, "%s", WiFi.localIP().toString().c_str()); + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +// +// some globals for the loop() +// + +long wakeup; +long last_wakeup = 0; +int first = 1; + +void loop() +{ + if (first) { + Serial.print("the loop, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + //vTaskPrioritySet( NULL, 2 ); + //print_ram(); + first = 0; + } + +#ifdef include_pir_and_touch + if (DeepSleepPir) { + if (recording == 0 && PIRenabled == 1) { + + delay(10000); // wait 10 seoonds for another event before sleep + + if (recording == 0 && PIRenabled == 1) { + + Serial.println("Going to sleep now"); + + pinMode(4, OUTPUT); + digitalWrite(4, LOW); + rtc_gpio_hold_en(GPIO_NUM_4); + gpio_deep_sleep_hold_en(); + digitalWrite(33, HIGH); + //rtc_gpio_hold_en(GPIO_NUM_33); + + esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, 1); + delay(500); + esp_deep_sleep_start(); + } + } + } +#endif + + count_loop++; + wakeup = millis(); + if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes + last_wakeup = millis(); + do_time(); + + //plm print_ram(); + } + +#ifdef include_telegram + if (send_a_telegram == 1) { // send the telegram after flag set, using the general heap + send_a_telegram = 0; + if (EnableBOT == 1 && Internet_Enabled == 1) { // just double-check + save_photo_dated(); + //send_photo_telegram(); + Wait_for_bot = 0; + } + } +#endif + + delay(1000); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// + +static esp_err_t capture_handler(httpd_req_t *req) { + + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + char fname[100]; + xSemaphoreTake( baton, portMAX_DELAY ); + + Serial.print("capture, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + fb = esp_camera_fb_get(); + + if (!fb) { + Serial.println("Camera capture failed"); + httpd_resp_send_500(req); + xSemaphoreGive( baton ); + return ESP_FAIL; + } + + file_number++; + + sprintf(fname, "inline; filename=capture_%d.jpg", file_number); + + httpd_resp_set_type(req, "image/jpeg"); + httpd_resp_set_hdr(req, "Content-Disposition", fname); + + size_t out_len, out_width, out_height; + size_t fb_len = 0; + fb_len = fb->len; + res = httpd_resp_send(req, (const char *)fb->buf, fb->len); + esp_camera_fb_return(fb); + xSemaphoreGive( baton ); + return res; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t stop_handler(httpd_req_t *req) { + + esp_err_t res = ESP_OK; + + recording = 0; + Serial.println("stopping recording"); + + do_stop(); + //do_stop("Stopping previous recording"); + xTaskNotifyGive(AviWriterTask); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; + +} + +void do_status(); // down below + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t pir_en_handler(httpd_req_t *req) { + + Serial.print("http pir_en, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + PIRenabled = 1; + do_eprom_write(); + do_status(); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t pir_dis_handler(httpd_req_t *req) { + + Serial.print("http pir_dis, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + PIRenabled = 0; + do_eprom_write(); + do_status(); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t bot_en_handler(httpd_req_t *req) { + + Serial.print("http bot_en, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + EnableBOT = 1; + do_eprom_write(); + do_status(); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t bot_dis_handler(httpd_req_t *req) { + + Serial.print("http bot_dis, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + EnableBOT = 0; + do_eprom_write(); + do_status(); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t start_handler(httpd_req_t *req) { + + esp_err_t res = ESP_OK; + + char buf[120]; + size_t buf_len; + char new_res[20]; + + if (recording == 1) { + const char* resp = "You must Stop recording, before starting a new one. Start over ..."; + httpd_resp_send(req, resp, strlen(resp)); + + return ESP_OK; + return res; + + } else { + //recording = 1; + Serial.println("starting recording"); + + sensor_t * s = esp_camera_sensor_get(); + + int new_interval = capture_interval; + int new_framesize = s->status.framesize; + int new_quality = s->status.quality; + int new_repeat = repeat_config; //v87 + int new_xspeed = xspeed; + int new_xlength = capture_interval * total_frames_config / 1000; // xlength; v88 + int new_gray = gray; + int new_bot = EnableBOT; + int new_pir = PIRenabled; + + + /* + Serial.println(""); + Serial.println("Current Parameters :"); + Serial.print(" Capture Interval = "); Serial.print(capture_interval); Serial.println(" ms"); + Serial.print(" Length = "); Serial.print(capture_interval * total_frames / 1000); Serial.println(" s"); + Serial.print(" Quality = "); Serial.println(new_quality); + Serial.print(" Framesize = "); Serial.println(new_framesize); + Serial.print(" Repeat = "); Serial.println(repeat); + Serial.print(" Speed = "); Serial.println(xspeed); + */ + + buf_len = httpd_req_get_url_query_len(req) + 1; + if (buf_len > 1) { + if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) { + ESP_LOGI(TAG, "Found URL query => %s", buf); + char param[32]; + /* Get value of expected key from query string */ + //Serial.println(" ... parameters"); + if (httpd_query_key_value(buf, "length", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 5 && x <= 3600 * 24 ) { // 5 sec to 24 hours + new_xlength = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => length=%s", param); + + } + if (httpd_query_key_value(buf, "repeat", param, sizeof(param)) == ESP_OK) { + int x = atoi(param); + if (x >= 0 ) { + new_repeat = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => repeat=%s", param); + } + //v99 - uxga 13, hd 11, svga 9, vga 8, cif 6 + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 13; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 9; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 8; + } else if (strcmp(new_res, "HD") == 0) { + new_framesize = 11; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 6; + } else { + Serial.println("Only UXGA, HD, SVGA, VGA, and CIF are valid!"); + + } + ESP_LOGI(TAG, "Found URL query parameter => framesize=%s", new_res); + } + if (httpd_query_key_value(buf, "quality", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 6 && x <= 50) { // MINIMUM QUALITY 10 to save memory + new_quality = x; // loosen rule to 6 to test bag_jpg v90 + } + + ESP_LOGI(TAG, "Found URL query parameter => quality=%s", param); + } + + if (httpd_query_key_value(buf, "speed", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 1 && x <= 10000) { + new_xspeed = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => speed=%s", param); + } + + if (httpd_query_key_value(buf, "gray", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x == 0 || x == 1 ) { + new_gray = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); + } + + if (httpd_query_key_value(buf, "pir", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x == 0 || x == 1 ) { + new_pir = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => pir=%s", param); + } + + if (httpd_query_key_value(buf, "bot", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x == 0 || x == 1 ) { + new_bot = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => bot=%s", param); + } + + if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 1 && x <= 300000) { // 300,000 ms = 5 min + new_interval = x; + } + ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); + } + } + } + + framesize = new_framesize; + capture_interval = new_interval; + xlength = new_xlength; + total_frames = new_xlength * 1000 / capture_interval; + total_frames_config = total_frames; + repeat = new_repeat; + repeat_config = new_repeat; + quality = new_quality; + xspeed = new_xspeed; + gray = new_gray; + EnableBOT = new_bot; + PIRenabled = new_pir; + + config_camera(); + + do_eprom_write(); + + do_start(); + httpd_resp_send(req, the_page, strlen(the_page)); + + + recording = 1; + xTaskNotifyGive(AviWriterTask); + + return ESP_OK; + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_start() { + const char the_message[] = "Starting a new AVI"; + + Serial.print("do_start "); Serial.println(the_message); + //v99 - uxga 13, hd 11, svga 9, vga 8, cif 6 + + const char msg[] PROGMEM = R"rawliteral( + + + + +%s ESP32-CAM Video Recorder + + +

%s
ESP32-CAM Video Recorder %s


+

%s


+ + Recording = %d (1 is active)
+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (10 best to 50 worst)
+ Framesize = %d (13 UXGA, 11 HD, 9 SVGA, 8 VGA, 6 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d
+ PIR = %d
+ BOT = %d

+ +
+ + + +)rawliteral"; + + + sprintf(the_page, msg, devname, devname, vernum, the_message, recording, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, PIRenabled, EnableBOT); + //Serial.println(strlen(msg)); + +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_stop() { + const char the_message[] = "Stopping previous recording"; + Serial.print("do_stop "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +%s ESP32-CAM Video Recorder + + +

%s
ESP32-CAM Video Recorder %s


+

%s


+

http://%s/

+
http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0&pir=1&bot=1 +
VGA 2 fps, for 30 minutes repeat, 15x playback +
UXGA 1 fps, for 30 minutes repeat, 30x playback, with bot +
HD 2 fps for 30 minutes repeat, 15x playback +
UXGA 5 sec per frame for 1 hour x150 repeat, Q12 +
SVGA 10fps for 10 min x2 repeat, with pir and bot +
UXGA 30 sec per frame for 2 hours repeat + +
+ +)rawliteral"; + + sprintf(the_page, msg, devname, devname, vernum, the_message, localip, localip, localip, localip, localip, localip, localip, localip, localip, localip); + +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_status() { + const char the_message[] = "Status"; + Serial.print("do_status "); Serial.println(the_message); + + elapsedms = millis() - startms; + + uint32_t ms_per_frame = 0; + int avg_frame_wrt = 0; + + if (frame_cnt > 0) { + ms_per_frame = elapsedms / frame_cnt; + avg_frame_wrt = totalw / frame_cnt ; + } + time(&now); + const char *strdate = ctime(&now); + + int tot = SD_MMC.totalBytes() / (1024 * 1024); + int use = SD_MMC.usedBytes() / (1024 * 1024); + long rssi = WiFi.RSSI(); + Serial.print("before msg "); + const char msg[] PROGMEM = R"rawliteral( + + + + +%s ESP32-CAM Video Recorder + + +

%s
ESP32-CAM Video Recorder %s
%s


+ + Used / Total SD Space %d MB / %d MB, Rssi %d, SD speed %d
+ Recording = %d, PIR Active = %d, PIR Enabled = %d, BOT Enabled = %d
+ Filename %s
+
+ Frame %d of %d, Skipped %d, jpeg: Normal %d, Extend %d, Bad %d
+ Capture Interval = %d ms, Actual Interval = %d ms, Avg Write time = %d ms, +

Length = %d seconds, Quality = %d (10 best to 50 worst)
+ Framesize = %d (13 UXGA, 11 HD, 9 SVGA, 8 VGA, 6 CIF)
+ Repeat = %d, Playback Speed = %d, Gray = %d
+
+

http://%s/

+

Stream at %d ms

+ pir_enable   + pir_disable
+ bot_enable   + bot_disable + +

http://%s/stop ... and restart. You must be stopped before restart or PIR

+

http://%s:8080/ ... esp32 sd file manager

+
+ +
+
+ +)rawliteral"; + Serial.print("after msg "); + delay(100); + print_ram(); + Serial.print(strlen(msg)); Serial.print(" "); Serial.println(localip); + + sprintf(the_page, msg, + devname, devname, vernum, + strdate, use, tot, rssi, diskspeed, + recording, PIRrecording, PIRenabled, EnableBOT, + fname, + frames_so_far, total_frames, skipped, normal_jpg, extend_jpg, bad_jpg, + capture_interval, ms_per_frame, avg_frame_wrt, + capture_interval * total_frames / 1000, quality, + framesize, + repeat, xspeed, gray, + localip, localip, localip, stream_interval, + localip, localip, localip, localip, localip, localip, localip, localip); + + Serial.println(strlen(the_page)); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t index_handler(httpd_req_t *req) { + Serial.print("http index, core "); Serial.print(xPortGetCoreID()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + do_status(); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + + +void startCameraServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.max_uri_handlers = 12; + //config.max_open_sockets = 2; + //config.task_priority = 1; //a1 + config.stack_size = 5000; //a1 + //Serial.print("Default task prio: "); Serial.println(config.task_priority); + //config.task_priority = 6; + //config.core_id = 0; + Serial.print("http task prio: "); Serial.println(config.task_priority); + //Serial.print("http task core: "); Serial.println(config.core_id); + + httpd_uri_t index_uri = { + .uri = "/", + .method = HTTP_GET, + .handler = index_handler, + .user_ctx = NULL + }; + httpd_uri_t capture_uri = { + .uri = "/capture", + .method = HTTP_GET, + .handler = capture_handler, + .user_ctx = NULL + }; + + httpd_uri_t file_stop = { + .uri = "/stop", + .method = HTTP_GET, + .handler = stop_handler, + .user_ctx = NULL + }; + + httpd_uri_t file_start = { + .uri = "/start", + .method = HTTP_GET, + .handler = start_handler, + .user_ctx = NULL + }; + + + httpd_uri_t file_pir_en = { + .uri = "/pir_enable", + .method = HTTP_GET, + .handler = pir_en_handler, + .user_ctx = NULL + }; + + httpd_uri_t file_pir_dis = { + .uri = "/pir_disable", + .method = HTTP_GET, + .handler = pir_dis_handler, + .user_ctx = NULL + }; + + httpd_uri_t file_bot_en = { + .uri = "/bot_enable", + .method = HTTP_GET, + .handler = bot_en_handler, + .user_ctx = NULL + }; + + httpd_uri_t file_bot_dis = { + .uri = "/bot_disable", + .method = HTTP_GET, + .handler = bot_dis_handler, + .user_ctx = NULL + }; + +#ifdef include_streaming + httpd_uri_t stream_uri = { + .uri = "/stream", + .method = HTTP_GET, + .handler = stream_handler, + .user_ctx = NULL + }; +#endif + + if (httpd_start(&camera_httpd, &config) == ESP_OK) { + httpd_register_uri_handler(camera_httpd, &index_uri); + httpd_register_uri_handler(camera_httpd, &capture_uri); + httpd_register_uri_handler(camera_httpd, &file_start); + httpd_register_uri_handler(camera_httpd, &file_stop); + + httpd_register_uri_handler(camera_httpd, &file_pir_en); + httpd_register_uri_handler(camera_httpd, &file_pir_dis); + httpd_register_uri_handler(camera_httpd, &file_bot_en); + httpd_register_uri_handler(camera_httpd, &file_bot_dis); +#ifdef include_streaming + httpd_register_uri_handler(camera_httpd, &stream_uri); +#endif + + } + + Serial.println("Camera http started"); +} diff --git a/vA1/UniversalTelegramBot.cpp b/vA1/UniversalTelegramBot.cpp new file mode 100644 index 0000000..6d03f18 --- /dev/null +++ b/vA1/UniversalTelegramBot.cpp @@ -0,0 +1,968 @@ +/* + Copyright (c) 2018 Brian Lough. All right reserved. + + UniversalTelegramBot - Library to create your own Telegram Bot using + ESP8266 or ESP32 on Arduino IDE. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + **** Note Regarding Client Connection Keeping **** + Client connection is established in functions that directly involve use of + client, i.e sendGetToTelegram, sendPostToTelegram, and + sendMultipartFormDataToTelegram. It is closed at the end of + sendMultipartFormDataToTelegram, but not at the end of sendGetToTelegram and + sendPostToTelegram as these may need to keep the connection alive for respose + / response checking. Re-establishing a connection then wastes time which is + noticeable in user experience. Due to this, it is important that connection + be closed manually after calling sendGetToTelegram or sendPostToTelegram by + calling closeClient(); Failure to close connection causes memory leakage and + SSL errors +*/ + +// James Zahary June 30, 2020 +// - small mods to add caption to photos, and slow down transmit to telegram +// James Zahary Jan 2, 2022 +// - Client to WiFiClientSecure due to esp32-arduino 2.02 + +#include "UniversalTelegramBot.h" + +UniversalTelegramBot::UniversalTelegramBot(String token, WiFiClientSecure &client) { // UniversalTelegramBot::UniversalTelegramBot(String token, Client &client) { + + _token = token; +#ifdef ARDUINO_ESP8266_RELEASE_2_5_0 + //client->setInsecure(); +#endif + this->client = &client; +} + +String UniversalTelegramBot::sendGetToTelegram(String command) { + String mess = ""; + long now; + bool avail; + + // Connect with api.telegram.org if not already connected + if (!client->connected()) { +#ifdef _debug + Serial.println(F("[BOT]Connecting to server")); +#endif + if (!client->connect(HOST, SSL_PORT)) { +#ifdef _debug + Serial.println(F("[BOT]Conection error")); +#endif + } + } + if (client->connected()) { + +#ifdef _debug + Serial.println(F(".... connected to server")); +#endif + + String a = ""; + char c; + int ch_count = 0; + client->println("GET /" + command); + now = millis(); + avail = false; + while (millis() - now < longPoll * 1000 + waitForResponse) { + while (client->available()) { + char c = client->read(); + if (ch_count < maxMessageLength) { + mess = mess + c; + ch_count++; + } + avail = true; + } + if (avail) { +#ifdef _debug + Serial.println(); + Serial.println(mess); + Serial.println(); +#endif + break; + } + } + } + + return mess; +} + +String UniversalTelegramBot::sendPostToTelegram(String command, JsonObject payload) { + + String body = ""; + String headers = ""; + long now; + bool responseReceived = false; + + // Connect with api.telegram.org if not already connected + if (!client->connected()) { +#ifdef _debug + Serial.println(F("[BOT Client]Connecting to server")); +#endif + if (!client->connect(HOST, SSL_PORT)) { +#ifdef _debug + Serial.println(F("[BOT Client]Conection error")); +#endif + } + } + if (client->connected()) { + // POST URI + client->print("POST /" + command); + client->println(F(" HTTP/1.1")); + delay(jzdelay); + // Host header + client->print(F("Host:")); + client->println(HOST); + delay(jzdelay); + // JSON content type + client->println(F("Content-Type: application/json")); + delay(jzdelay); + + // Content length + int length = measureJson(payload); + client->print(F("Content-Length:")); + client->println(length); + delay(jzdelay); + // End of headers + client->println(); + // POST message body + String out; + serializeJson(payload, out); + + client->println(out); + delay(jzdelay); + + int ch_count = 0; + now = millis(); + bool finishedHeaders = false; + bool currentLineIsBlank = true; + while (millis() - now < waitForResponse) { + while (client->available()) { + char c = client->read(); + responseReceived = true; + + if (!finishedHeaders) { + if (currentLineIsBlank && c == '\n') { + finishedHeaders = true; + } else { + headers = headers + c; + } + } else { + if (ch_count < maxMessageLength) { + body = body + c; + ch_count++; + } + } + + if (c == '\n') currentLineIsBlank = true; + else if (c != '\r') currentLineIsBlank = false; + + } + + if (responseReceived && ch_count > 5) { //jz +#ifdef _debug + Serial.println(); + Serial.println(body); + Serial.println(); +#endif + //Serial.print(millis() - now); Serial.println(" sendPostToTelegram - breaking"); + break; + } + } + } + + return body; +} + +String UniversalTelegramBot::sendMultipartFormDataToTelegram( + String command, String binaryProperyName, String fileName, + String contentType, String chat_id, int fileSize, + MoreDataAvailable moreDataAvailableCallback, + GetNextByte getNextByteCallback, + GetNextBuffer getNextBufferCallback, + GetNextBufferLen getNextBufferLenCallback) { + + String body = ""; + String headers = ""; + long now; + bool responseReceived = false; + bool finishedHeaders = false; + bool currentLineIsBlank = true; + + String boundry = F("------------------------b8f610217e83e29b"); + + // Connect with api.telegram.org if not already connected + if (!client->connected()) { +#ifdef _debug + Serial.println(F("[BOT Client]Connecting to server")); +#endif + if (!client->connect(HOST, SSL_PORT)) { +#ifdef _debug + Serial.println(F("[BOT Client]Conection error")); +#endif + } + } + if (client->connected()) { + + String start_request = ""; + String end_request = ""; + + start_request = start_request + "--" + boundry + "\r\n"; + start_request = start_request + "content-disposition: form-data; name=\"chat_id\"" + "\r\n"; + start_request = start_request + "\r\n"; + start_request = start_request + chat_id + "\r\n"; + + start_request = start_request + "--" + boundry + "\r\n"; + start_request = start_request + "content-disposition: form-data; name=\"caption\"" + "\r\n"; + start_request = start_request + "\r\n"; + start_request = start_request + "caption here!" + "\r\n"; + + start_request = start_request + "--" + boundry + "\r\n"; + start_request = start_request + "content-disposition: form-data; name=\"" + binaryProperyName + "\"; filename=\"" + fileName + "\"" + "\r\n"; + + start_request = start_request + "Content-Type: " + contentType + "\r\n"; + start_request = start_request + "\r\n"; + + end_request = end_request + "\r\n"; + end_request = end_request + "--" + boundry + "--" + "\r\n"; + + client->print("POST /bot" + _token + "/" + command); + client->println(F(" HTTP/1.1")); + // Host header + client->print(F("Host: ")); + client->println(HOST); + client->println(F("User-Agent: arduino/1.0")); + Serial.print("*") ; delay(jzdelay); //jz + client->println(F("Accept: */*")); + Serial.print("*") ; delay(jzdelay); //jz + + int contentLength = fileSize + start_request.length() + end_request.length(); +#ifdef _debug + Serial.println("Content-Length: " + String(contentLength)); +#endif + client->print("Content-Length: "); + client->println(String(contentLength)); + client->println("Content-Type: multipart/form-data; boundary=" + boundry); + Serial.print("*") ; delay(jzdelay); //jz + client->println(); //v99 - ssl not happy + Serial.print("*") ; delay(jzdelay); //jz + client->print(start_request); + Serial.print("Start request: " + start_request); +#ifdef _debug + Serial.print("Start request: " + start_request); +#endif + + if (getNextByteCallback == nullptr) { + while (moreDataAvailableCallback()) { + client->write((const uint8_t *)getNextBufferCallback(), getNextBufferLenCallback()); +#ifdef _debug + Serial.println(F("Sending photo from buffer")); +#endif + } + } else { +#ifdef _debug + Serial.println(F("Sending photo by binary")); +#endif + byte buffer[jzblocksize]; //jz 512 + int count = 0; + char ch; + while (moreDataAvailableCallback()) { + buffer[count] = getNextByteCallback(); + count++; + if (count == jzblocksize) { //jz 512 + // yield(); +#ifdef _debug + //Serial.println(F("Sending binary photo full buffer")); +#endif + client->write((const uint8_t *)buffer, jzblocksize); //jz 512 + Serial.print("*") ; delay(jzdelay); //jz + count = 0; + } + } + + if (count > 0) { +#ifdef _debug + Serial.println(F("Sending binary photo remaining buffer")); +#endif + client->write((const uint8_t *)buffer, count); + Serial.print("*") ; delay(jzdelay); //jz + } + } + + client->print(end_request); + //#ifdef _debug + Serial.print("End request: " + end_request); + //#endif + + Serial.print("... Done Sending. Client.Available = "); Serial.println(client->available()); + delay(2000); + Serial.print("... 2 secs later. Client.Available = "); Serial.println(client->available()); + + int ch_count = 0; + now = millis(); + + while (millis() - now < waitForResponse) { + while (client->available()) { + char c = client->read(); + responseReceived = true; + + if (!finishedHeaders) { + if (currentLineIsBlank && c == '\n') { + finishedHeaders = true; + } else { + headers = headers + c; + } + } else { + if (ch_count < maxMessageLength) { + body = body + c; + ch_count++; + } + } + + if (c == '\n') currentLineIsBlank = true; + else if (c != '\r') currentLineIsBlank = false; + } + + if (responseReceived && ch_count > 5) { //jz && ch_count > 5 +#ifdef _debug + Serial.println(); + Serial.println(body); + Serial.println(); +#endif + //Serial.print(millis() - now); Serial.println(" sendMultipartFormDataToTelegram - breaking"); + break; + } + } + } + + closeClient(); + return body; +} + +String UniversalTelegramBot::sendMultipartFormDataToTelegramWithCaption( + String command, String binaryProperyName, String fileName, + String contentType, String caption, String chat_id, int fileSize, + MoreDataAvailable moreDataAvailableCallback, + GetNextByte getNextByteCallback, + GetNextBuffer getNextBufferCallback, + GetNextBufferLen getNextBufferLenCallback) { + + String body = ""; + String headers = ""; + long now; + bool responseReceived = false; + bool finishedHeaders = false; + bool currentLineIsBlank = true; + + String boundry = F("------------------------b8f610217e83e29b"); + + // Connect with api.telegram.org if not already connected + if (!client->connected()) { +#ifdef _debug + Serial.println(F("[BOT Client]Connecting to server")); +#endif + //client->stop(); + //client -> setInsecure(); //a1 + //client -> setTimeout(2); + //client -> setHandshakeTimeout(120000); //120000 + int x = client->connect(HOST, SSL_PORT); + if (x == 0) { +#ifdef _debug + Serial.println(F("[BOT Client]Conection error")); + delay(2000); + int x = client->connect(HOST, SSL_PORT); + delay(2000); + if (!client->connected()) { + Serial.println(F("[BOT Client]Another Conection error")); + delay(2000); + } +#endif + } + } + if (client->connected()) { + + String start_request = ""; + String end_request = ""; + + + //Serial.print("Start: "); Serial.println(ESP.getFreeHeap()); + + start_request = start_request + "--" + boundry + "\r\n"; + start_request = start_request + "content-disposition: form-data; name=\"chat_id\"" + "\r\n"; + start_request = start_request + "\r\n"; + start_request = start_request + chat_id + "\r\n"; + + start_request = start_request + "--" + boundry + "\r\n"; //jz caption stuff + start_request = start_request + "content-disposition: form-data; name=\"caption\"" + "\r\n"; + start_request = start_request + "\r\n"; + start_request = start_request + caption + "\r\n"; + + start_request = start_request + "--" + boundry + "\r\n"; + start_request = start_request + "content-disposition: form-data; name=\"" + binaryProperyName + "\"; filename=\"" + fileName + "\"" + "\r\n"; + + start_request = start_request + "Content-Type: " + contentType + "\r\n"; + start_request = start_request + "\r\n"; + + end_request = end_request + "\r\n"; + end_request = end_request + "--" + boundry + "--" + "\r\n"; + + client->print("POST /bot" + _token + "/" + command); + client->println(F(" HTTP/1.1")); + // Host header + client->print(F("Host: ")); + client->println(HOST); + client->println(F("User-Agent: arduino/1.0")); + Serial.print("*") ; delay(jzdelay); //jz + client->println(F("Accept: */*")); + Serial.print("*") ; delay(jzdelay); //jz + + int contentLength = fileSize + start_request.length() + end_request.length(); +#ifdef _debug + Serial.println("Content-Length: " + String(contentLength)); +#endif + client->print("Content-Length: "); + client->println(String(contentLength)); + client->println("Content-Type: multipart/form-data; boundary=" + boundry); + Serial.print("*") ; delay(jzdelay); //jz + client->println(); //v99 + Serial.print("*") ; delay(jzdelay); //jz + client->print(start_request); + +#ifdef _debug + Serial.print("Start request: " + start_request); +#endif + + //Serial.print("End: "); Serial.println(ESP.getFreeHeap()); + + if (getNextByteCallback == nullptr) { + while (moreDataAvailableCallback()) { + client->write((const uint8_t *)getNextBufferCallback(), getNextBufferLenCallback()); +#ifdef _debug + Serial.println(F("Sending photo from buffer")); +#endif + } + } else { +#ifdef _debug + Serial.println(F("Sending photo by binary")); +#endif + byte buffer[jzblocksize]; + int count = 0; + char ch; + while (moreDataAvailableCallback()) { + buffer[count] = getNextByteCallback(); + count++; + if (count == jzblocksize) { + // yield(); +#ifdef _debug + //Serial.println(F("Sending binary photo full buffer")); +#endif + client->write((const uint8_t *)buffer, jzblocksize); + Serial.print("*") ; delay(jzdelay); //jz + count = 0; + } + } + + if (count > 0) { +#ifdef _debug + Serial.println(F("Sending binary photo remaining buffer")); +#endif + client->write((const uint8_t *)buffer, count); + Serial.print("*") ; delay(jzdelay); //jz + } + } + + client->print(end_request); +#ifdef _debug + Serial.print("End request: " + end_request); + + Serial.print("... Done Sending. Client.Available = "); Serial.println(client->available()); + delay(2000); + Serial.print("... 2 secs later. Client.Available = "); Serial.println(client->available()); +#endif + + int ch_count = 0; + now = millis(); + + while (millis() - now < waitForResponse) { + while (client->available()) { + char c = client->read(); + responseReceived = true; + + if (!finishedHeaders) { + if (currentLineIsBlank && c == '\n') { + finishedHeaders = true; + } else { + headers = headers + c; + } + } else { + if (ch_count < maxMessageLength) { + body = body + c; + ch_count++; + } + } + + if (c == '\n') currentLineIsBlank = true; + else if (c != '\r') currentLineIsBlank = false; + } + + if (responseReceived && ch_count > 5) { //jz && ch_count > 5 +#ifdef _debug + Serial.println(); + Serial.println(body); + Serial.println(); +#endif + //Serial.print(millis() - now); Serial.println(" sendMultipartFormDataToTelegram - breaking"); + break; + } + } + } + + closeClient(); + return body; +} + +bool UniversalTelegramBot::getMe() { + String command = "bot" + _token + "/getMe"; + String response = sendGetToTelegram(command); // receive reply from telegram.org + DynamicJsonDocument doc(maxMessageLength); + DeserializationError error = deserializeJson(doc, response); + JsonObject obj = doc.as(); //there is nothing better right now to use obj.containsKey("result") + closeClient(); + + if (!error) { + if (obj.containsKey("result")) { + String _name = doc["result"]["first_name"]; + String _username = doc["result"]["username"]; + name = _name; + userName = _username; + return true; + } + } + + return false; +} + +/*************************************************************** + GetUpdates - function to receive messages from telegram + (Argument to pass: the last+1 message to read) + Returns the number of new messages + ***************************************************************/ +int UniversalTelegramBot::getUpdates(long offset) { + +#ifdef _debug + Serial.println(F("GET Update Messages")); +#endif + String command = "bot" + _token + "/getUpdates?offset=" + String(offset) + "&limit=" + String(HANDLE_MESSAGES); + if (longPoll > 0) { + command = command + "&timeout=" + String(longPoll); + } + String response = sendGetToTelegram(command); // receive reply from telegram.org + + if (response == "") { +#ifdef _debug + Serial.println(F("Received empty string in response!")); +#endif + // close the client as there's nothing to do with an empty string + closeClient(); + return 0; + } else { +#ifdef _debug + Serial.print(F("incoming message length ")); + Serial.println(response.length()); + Serial.println(F("Creating DynamicJsonBuffer")); +#endif + + // Parse response into Json object + DynamicJsonDocument doc(maxMessageLength); + DeserializationError error = deserializeJson(doc, response); +#ifdef _debug + Serial.print(F("GetUpdates parsed jsonDoc: ")); + serializeJson(doc, Serial); + Serial.println(); +#endif + + JsonObject obj = doc.as(); //there is nothing better right now + if (!error) { +#ifdef _debug + Serial.print(F("GetUpdates parsed jsonObj: ")); + serializeJson(obj, Serial); + Serial.println(); +#endif + if (obj.containsKey("result")) { + int resultArrayLength = doc["result"].size(); + if (resultArrayLength > 0) { + int newMessageIndex = 0; + // Step through all results + for (int i = 0; i < resultArrayLength; i++) { + JsonObject result = doc["result"][i]; + if (processResult(result, newMessageIndex)) newMessageIndex++; + } + // We will keep the client open because there may be a response to be + // given + return newMessageIndex; + } else { +#ifdef _debug + Serial.println(F("no new messages")); +#endif + } + } else { +#ifdef _debug + Serial.println(F("Response contained no 'result'")); +#endif + } + } else { // Parsing failed + if (response.length() < 2) { // Too short a message. Maybe a connection issue +#ifdef _debug + Serial.println(F("Parsing error: Message too short")); +#endif + } else { + // Buffer may not be big enough, increase buffer or reduce max number of + // messages +#ifdef _debug + Serial.print(F("Failed to parse update, the message could be too " + "big for the buffer. Error code: ")); + Serial.println(error.c_str()); // debug print of parsing error +#endif + } + } + // Close the client as no response is to be given + closeClient(); + return 0; + } +} + +bool UniversalTelegramBot::processResult(JsonObject result, int messageIndex) { + int update_id = result["update_id"]; + // Check have we already dealt with this message (this shouldn't happen!) + if (last_message_received != update_id) { + last_message_received = update_id; + messages[messageIndex].update_id = update_id; + messages[messageIndex].text = F(""); + messages[messageIndex].from_id = F(""); + messages[messageIndex].from_name = F(""); + messages[messageIndex].longitude = 0; + messages[messageIndex].latitude = 0; + + if (result.containsKey("message")) { + JsonObject message = result["message"]; + messages[messageIndex].type = F("message"); + messages[messageIndex].from_id = message["from"]["id"].as(); + messages[messageIndex].from_name = message["from"]["first_name"].as(); + messages[messageIndex].date = message["date"].as(); + messages[messageIndex].chat_id = message["chat"]["id"].as(); + messages[messageIndex].chat_title = message["chat"]["title"].as(); + + if (message.containsKey("text")) { + messages[messageIndex].text = message["text"].as(); + + } else if (message.containsKey("location")) { + messages[messageIndex].longitude = message["location"]["longitude"].as(); + messages[messageIndex].latitude = message["location"]["latitude"].as(); + } + } else if (result.containsKey("channel_post")) { + JsonObject message = result["channel_post"]; + messages[messageIndex].type = F("channel_post"); + messages[messageIndex].text = message["text"].as(); + messages[messageIndex].date = message["date"].as(); + messages[messageIndex].chat_id = message["chat"]["id"].as(); + messages[messageIndex].chat_title = message["chat"]["title"].as(); + + } else if (result.containsKey("callback_query")) { + JsonObject message = result["callback_query"]; + messages[messageIndex].type = F("callback_query"); + messages[messageIndex].from_id = message["from"]["id"].as(); + messages[messageIndex].from_name = message["from"]["first_name"].as(); + messages[messageIndex].text = message["data"].as(); + messages[messageIndex].date = message["date"].as(); + messages[messageIndex].chat_id = message["message"]["chat"]["id"].as(); + messages[messageIndex].chat_title = F(""); + + } else if (result.containsKey("edited_message")) { + JsonObject message = result["edited_message"]; + messages[messageIndex].type = F("edited_message"); + messages[messageIndex].from_id = message["from"]["id"].as(); + messages[messageIndex].from_name = message["from"]["first_name"].as(); + messages[messageIndex].date = message["date"].as(); + messages[messageIndex].chat_id = message["chat"]["id"].as(); + messages[messageIndex].chat_title = message["chat"]["title"].as(); + + if (message.containsKey("text")) { + messages[messageIndex].text = message["text"].as(); + + } else if (message.containsKey("location")) { + messages[messageIndex].longitude = message["location"]["longitude"].as(); + messages[messageIndex].latitude = message["location"]["latitude"].as(); + } + } + return true; + } + return false; +} + +/*********************************************************************** + SendMessage - function to send message to telegram + (Arguments to pass: chat_id, text to transmit and markup(optional)) + ***********************************************************************/ +bool UniversalTelegramBot::sendSimpleMessage(String chat_id, String text, + String parse_mode) { + + bool sent = false; +#ifdef _debug + Serial.println(F("sendSimpleMessage: SEND Simple Message")); +#endif + long sttime = millis(); + + if (text != "") { + while (millis() < sttime + 8000) { // loop for a while to send the message + String command = "bot" + _token + "/sendMessage?chat_id=" + chat_id + + "&text=" + text + "&parse_mode=" + parse_mode; + String response = sendGetToTelegram(command); +#ifdef _debug + Serial.println(response); +#endif + sent = checkForOkResponse(response); + if (sent) break; + } + } + closeClient(); + return sent; +} + +bool UniversalTelegramBot::sendMessage(String chat_id, String text, + String parse_mode) { + + DynamicJsonDocument payload(maxMessageLength); + payload["chat_id"] = chat_id; + payload["text"] = text; + + if (parse_mode != "") + payload["parse_mode"] = parse_mode; + + return sendPostMessage(payload.as()); +} + +bool UniversalTelegramBot::sendMessageWithReplyKeyboard( + String chat_id, String text, String parse_mode, String keyboard, + bool resize, bool oneTime, bool selective) { + + DynamicJsonDocument payload(maxMessageLength); + payload["chat_id"] = chat_id; + payload["text"] = text; + + if (parse_mode != "") + payload["parse_mode"] = parse_mode; + + JsonObject replyMarkup = payload.createNestedObject("reply_markup"); + + // Reply keyboard is an array of arrays. + // Outer array represents rows + // Inner arrays represents columns + // This example "ledon" and "ledoff" are two buttons on the top row + // and "status is a single button on the next row" + DynamicJsonDocument keyboardBuffer(maxMessageLength); // creating a buffer enough to keep keyboard string + deserializeJson(keyboardBuffer, keyboard); + replyMarkup["keyboard"] = keyboardBuffer.as(); + + // Telegram defaults these values to false, so to decrease the size of the + // payload we will only send them if needed + if (resize) + replyMarkup["resize_keyboard"] = resize; + + if (oneTime) + replyMarkup["one_time_keyboard"] = oneTime; + + if (selective) + replyMarkup["selective"] = selective; + + return sendPostMessage(payload.as()); +} + +bool UniversalTelegramBot::sendMessageWithInlineKeyboard(String chat_id, + String text, + String parse_mode, + String keyboard) { + + DynamicJsonDocument payload(maxMessageLength); + payload["chat_id"] = chat_id; + payload["text"] = text; + + if (parse_mode != "") + payload["parse_mode"] = parse_mode; + + JsonObject replyMarkup = payload.createNestedObject("reply_markup"); + DynamicJsonDocument keyboardBuffer(maxMessageLength); // assuming keyboard buffer will alwas be limited to 1024 bytes + deserializeJson(keyboardBuffer, keyboard); + replyMarkup["inline_keyboard"] = keyboardBuffer.as(); + return sendPostMessage(payload.as()); +} + +/*********************************************************************** + SendPostMessage - function to send message to telegram + (Arguments to pass: chat_id, text to transmit and markup(optional)) + ***********************************************************************/ +bool UniversalTelegramBot::sendPostMessage(JsonObject payload) { + + bool sent = false; +#ifdef _debug + Serial.print(F("sendPostMessage: SEND Post Message: ")); + serializeJson(payload, Serial); + Serial.println(); +#endif + long sttime = millis(); + + if (payload.containsKey("text")) { + while (millis() < sttime + 8000) { // loop for a while to send the message + String command = "bot" + _token + "/sendMessage"; + String response = sendPostToTelegram(command, payload); +#ifdef _debug + Serial.println(response); +#endif + sent = checkForOkResponse(response); + if (sent) break; + } + } + + closeClient(); + return sent; +} + +String UniversalTelegramBot::sendPostPhoto(JsonObject payload) { + + bool sent = false; + String response = ""; +#ifdef _debug + Serial.println(F("sendPostPhoto: SEND Post Photo")); +#endif + long sttime = millis(); + + if (payload.containsKey("photo")) { + while (millis() < sttime + 8000) { // loop for a while to send the message + String command = "bot" + _token + "/sendPhoto"; + response = sendPostToTelegram(command, payload); +#ifdef _debug + Serial.println(response); +#endif + sent = checkForOkResponse(response); + if (sent) break; + + } + } + + closeClient(); + return response; +} + +String UniversalTelegramBot::sendPhotoByBinary( + String chat_id, String contentType, int fileSize, + MoreDataAvailable moreDataAvailableCallback, + GetNextByte getNextByteCallback, GetNextBuffer getNextBufferCallback, GetNextBufferLen getNextBufferLenCallback) { + +#ifdef _debug + Serial.println(F("sendPhotoByBinary: SEND Photo")); +#endif + + String response = sendMultipartFormDataToTelegram("sendPhoto", "photo", "img.jpg", + contentType, chat_id, fileSize, + moreDataAvailableCallback, getNextByteCallback, getNextBufferCallback, getNextBufferLenCallback); + +#ifdef _debug + Serial.println(response); +#endif + + return response; +} + + + +String UniversalTelegramBot::sendPhoto(String chat_id, String photo, + String caption, + bool disable_notification, + int reply_to_message_id, + String keyboard) { + + DynamicJsonDocument payload(maxMessageLength); + payload["chat_id"] = chat_id; + payload["photo"] = photo; + + if (caption) + payload["caption"] = caption; + + if (disable_notification) + payload["disable_notification"] = disable_notification; + + if (reply_to_message_id && reply_to_message_id != 0) + payload["reply_to_message_id"] = reply_to_message_id; + + if (keyboard) { + JsonObject replyMarkup = payload.createNestedObject("reply_markup"); + DynamicJsonDocument keyboardBuffer(maxMessageLength); // assuming keyboard buffer will alwas be limited to 1024 bytes + deserializeJson(keyboardBuffer, keyboard); + replyMarkup["keyboard"] = keyboardBuffer.as(); + } + + return sendPostPhoto(payload.as()); +} + +bool UniversalTelegramBot::checkForOkResponse(String response) { + int responseLength = response.length(); + + for (int m = 5; m < responseLength + 1; m++) { + if (response.substring(m - 10, m) == + "{\"ok\":true") { // Chek if message has been properly sent + return true; + } + } + + return false; +} + +bool UniversalTelegramBot::sendChatAction(String chat_id, String text) { + + bool sent = false; +#ifdef _debug + Serial.println(F("SEND Chat Action Message")); +#endif + long sttime = millis(); + + if (text != "") { + while (millis() < sttime + 8000) { // loop for a while to send the message + String command = "bot" + _token + "/sendChatAction?chat_id=" + chat_id + + "&action=" + text; + String response = sendGetToTelegram(command); + +#ifdef _debug + Serial.println(response); +#endif + sent = checkForOkResponse(response); + + if (sent) break; + + } + } + + closeClient(); + return sent; +} + +void UniversalTelegramBot::closeClient() { + if (client->connected()) { +#ifdef _debug + Serial.println(F("Closing client")); +#endif + client->stop(); + client -> setHandshakeTimeout(120000); // bug in esp32-arduino 2.0.2 + } +} diff --git a/vA1/UniversalTelegramBot.h b/vA1/UniversalTelegramBot.h new file mode 100644 index 0000000..5f1c9da --- /dev/null +++ b/vA1/UniversalTelegramBot.h @@ -0,0 +1,129 @@ +/* +Copyright (c) 2018 Brian Lough. All right reserved. + +UniversalTelegramBot - Library to create your own Telegram Bot using +ESP8266 or ESP32 on Arduino IDE. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UniversalTelegramBot_h +#define UniversalTelegramBot_h + +#define ARDUINOJSON_DECODE_UNICODE 1 +#define ARDUINOJSON_USE_LONG_LONG 1 +#include +#include +#include +#include + +// a1 +#include + +#define HOST "api.telegram.org" +#define SSL_PORT 443 +#define HANDLE_MESSAGES 1 + +//unmark following line to enable debug mode +//#define _debug + +typedef bool (*MoreDataAvailable)(); +typedef byte (*GetNextByte)(); +typedef byte* (*GetNextBuffer)(); +typedef int (GetNextBufferLen)(); + +struct telegramMessage { + String text; + String chat_id; + String chat_title; + String from_id; + String from_name; + String date; + String type; + float longitude; + float latitude; + int update_id; +}; + +class UniversalTelegramBot { +public: + UniversalTelegramBot(String token, WiFiClientSecure &client); //UniversalTelegramBot(String token, Client &client); + + String sendGetToTelegram(String command); + String sendPostToTelegram(String command, JsonObject payload); + String + sendMultipartFormDataToTelegram(String command, String binaryProperyName, + String fileName, String contentType, + String chat_id, int fileSize, + MoreDataAvailable moreDataAvailableCallback, + GetNextByte getNextByteCallback, + GetNextBuffer getNextBufferCallback, + GetNextBufferLen getNextBufferLenCallback); + + String + sendMultipartFormDataToTelegramWithCaption(String command, String binaryProperyName, + String fileName, String contentType, + String caption, String chat_id, int fileSize, + MoreDataAvailable moreDataAvailableCallback, + GetNextByte getNextByteCallback, + GetNextBuffer getNextBufferCallback, + GetNextBufferLen getNextBufferLenCallback); + + + bool getMe(); + + bool sendSimpleMessage(String chat_id, String text, String parse_mode); + bool sendMessage(String chat_id, String text, String parse_mode = ""); + bool sendMessageWithReplyKeyboard(String chat_id, String text, + String parse_mode, String keyboard, + bool resize = false, bool oneTime = false, + bool selective = false); + bool sendMessageWithInlineKeyboard(String chat_id, String text, + String parse_mode, String keyboard); + + bool sendChatAction(String chat_id, String text); + + bool sendPostMessage(JsonObject payload); + String sendPostPhoto(JsonObject payload); + String sendPhotoByBinary(String chat_id, String contentType, int fileSize, + MoreDataAvailable moreDataAvailableCallback, + GetNextByte getNextByteCallback, + GetNextBuffer getNextBufferCallback, + GetNextBufferLen getNextBufferLenCallback); + String sendPhoto(String chat_id, String photo, String caption = "", + bool disable_notification = false, + int reply_to_message_id = 0, String keyboard = ""); + + int getUpdates(long offset); + bool checkForOkResponse(String response); + telegramMessage messages[HANDLE_MESSAGES]; + long last_message_received; + String name; + String userName; + int longPoll = 0; + int waitForResponse = 5000; //jz = 1500; + int jzdelay = 10; //60; // delay between multipart blocks + int jzblocksize = 2 * 1024; // multipart block size + +private: + // JsonObject * parseUpdates(String response); + String _token; + WiFiClientSecure *client; //Client *client; //a1 2.02 esp32-arduino + void closeClient(); + const int maxMessageLength = 1500; //was 1500 + bool processResult(JsonObject result, int messageIndex); +}; + +#endif diff --git a/vA1/settings.h b/vA1/settings.h new file mode 100644 index 0000000..f8a88a9 --- /dev/null +++ b/vA1/settings.h @@ -0,0 +1,65 @@ +static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames + +#define include_telegram +//#define include_pir_and_touch +#define include_ftp +#define include_streaming +#define get_rid_of_touch + +int delete_old_files = 1; // set to 1 and it will delete your oldest day of files so you SD is always 10% empty + +// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here +#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT + +// 1 for blink red led with every sd card write, at your frame rate +// 0 for blink only for skipping frames and SOS if camera or sd is broken +#define BlinkWithWrite 1 + +// EDIT ssid and password **** with Version 98x-WiFiMan, you are using WiFiManager to set ssid and password, so these are redundant +const char* ssid = "jzjzjzjz"; +const char* password = "mrpeanut"; + +// reboot startup parameters here + +int Internet_Enabled = 1; // set to 0 to shut off all internet activities - wifi, time, http, ftp, telegram +int DeepSleepPir = 0; // set to 1 to deepsleep between pir videos +int record_on_reboot = 1; // set to 1 to record, or 0 to NOT record on reboot +int PIRpin = 13; // for active high pir or microwave etc +int PIRenabled = 0; // 1 is PIR is enable on reboot, will only work if you are not recording + +int MagicNumber = 011; // change this if you are re-compiling and you dont want to use the ESPROM settings +int stream_interval = 333; // milliseconds between frames delivered during the live stream - 333 is 3 fps + +// here are 2 sets of startup parameters -- more down in the stop and restart webpage + +// VGA 10 fps for 30 minutes, and repeat, play at real time + +int framesize = 8; // 13 UXGA, 11 HD, 9 SVGA, 8 VGA, 6 CIF +int repeat_config = 100; // repaeat same movie this many times +int xspeed = 1; // playback speed - realtime is 1, or 300 means playpack 30 fps of frames at 10 second per frame ( 30 fps / 0.1 fps ) +int gray = 0; // not gray +int quality = 12; // quality on the 10..50 subscale - 10 is good, 20 is grainy and smaller files, 12 is better in bright sunshine due to clipping +int capture_interval = 100; // milli-seconds between frames +volatile int total_frames_config = 18000; // how many frames - length of movie in ms is total_frames x capture_interval + + +// UXGA 1 frame every 10 seconds for 60 minutes, and repeat, play at 30 fps or 300 times speed +/* +int framesize = 13; // 13 UXGA, 11 HD, 9 SVGA, 8 VGA, 6 CIF +int repeat_config = 300; // repaeat same movie this many times +int xspeed = 300; // playback speed - realtime is 1, or 300 means playpack 30 fps of frames at 10 second per frames ( 30 fps / 0.1 fps ) +int gray = 0; // not gray +int quality = 6; // quality on the 10..50 subscale - 10 is good, 20 is grainy and smaller files, 12 is better in bright sunshine due to clipping +int capture_interval = 10000; // milli-seconds between frames +volatile int total_frames_config = 360; // how many frames - length of movie is total_frames x capture_interval +*/ + +// enable the www.telegram.org BOT - it sends a text and and snapshot to you every time it starts a video +// https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot +// I'm using the branch v1.2 from June 2020 - new master introduced late june, but not working for picture and captions, so my v1.2 mods included here +// You need to create a bot, and get its number BOTtoken, and then get your telegram number -- all free at telegram.org +// detailed instructions here https://randomnerdtutorials.com/telegram-control-esp32-esp8266-nodemcu-outputs/ + +RTC_DATA_ATTR int EnableBOT = 0; +#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org +#define BOTme "1234567890"