From 7856f05a52cc5166f196ca7c9d0946d9cd7cb1ae Mon Sep 17 00:00:00 2001
From: jameszah <36938190+jameszah@users.noreply.github.com>
Date: Wed, 1 Sep 2021 15:44:47 -0600
Subject: [PATCH] Sep 1, 2021 version 99 for esp32-arduino 1.0.6
---
v99/ESP32FtpServer.cpp | 1173 ++++++++++++++
v99/ESP32FtpServer.h | 116 ++
v99/TimeLapseAvi99x.ino | 2798 ++++++++++++++++++++++++++++++++++
v99/UniversalTelegramBot.cpp | 952 ++++++++++++
v99/UniversalTelegramBot.h | 125 ++
v99/rtc_cntl.h | 64 +
v99/settings.h | 66 +
7 files changed, 5294 insertions(+)
create mode 100644 v99/ESP32FtpServer.cpp
create mode 100644 v99/ESP32FtpServer.h
create mode 100644 v99/TimeLapseAvi99x.ino
create mode 100644 v99/UniversalTelegramBot.cpp
create mode 100644 v99/UniversalTelegramBot.h
create mode 100644 v99/rtc_cntl.h
create mode 100644 v99/settings.h
diff --git a/v99/ESP32FtpServer.cpp b/v99/ESP32FtpServer.cpp
new file mode 100644
index 0000000..c9323aa
--- /dev/null
+++ b/v99/ESP32FtpServer.cpp
@@ -0,0 +1,1173 @@
+/*
+ * FTP Serveur for ESP8266
+ * based on FTP Serveur for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200)
+ * based on Jean-Michel Gallego's work
+ * modified to work with esp8266 SPIFFS by David Paiva david@nailbuster.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+// 2017: modified by @robo8080
+// 2019: modified by @fa1ke5
+
+#include "ESP32FtpServer.h"
+
+#include
+//#include
+#include
+#include "SD_MMC.h"
+//#include "SPI.h"
+
+#include //jz feb2020
+
+extern int count_ftp2;
+
+WiFiServer ftpServer( FTP_CTRL_PORT );
+WiFiServer dataServer( FTP_DATA_PORT_PASV );
+
+void FtpServer::begin(String uname, String pword)
+{
+ // Tells the ftp server to begin listening for incoming connection
+ _FTP_USER=uname;
+ _FTP_PASS = pword;
+
+ ftpServer.begin();
+ delay(10);
+ dataServer.begin();
+ delay(10);
+ millisTimeOut = (uint32_t)FTP_TIME_OUT * 60 * 1000;
+ millisDelay = 0;
+ cmdStatus = 0;
+ iniVariables();
+}
+
+void FtpServer::iniVariables()
+{
+ // Default for data port
+ dataPort = FTP_DATA_PORT_PASV;
+
+ // Default Data connection is Active
+ dataPassiveConn = true;
+
+ // Set the root directory
+ strcpy( cwdName, "/" );
+
+ rnfrCmd = false;
+ transferStatus = 0;
+
+}
+
+void FtpServer::handleFTP()
+{
+ if((int32_t) ( millisDelay - millis() ) > 0 )
+ return;
+
+ if (ftpServer.hasClient()) {
+// if (ftpServer.available()) {
+ client.stop();
+ client = ftpServer.available();
+ }
+
+ if( cmdStatus == 0 )
+ {
+ if( client.connected())
+ disconnectClient();
+ cmdStatus = 1;
+ }
+ else if( cmdStatus == 1 ) // Ftp server waiting for connection
+ {
+ abortTransfer();
+ iniVariables();
+ #ifdef FTP_DEBUG
+ Serial.println("Ftp server waiting for connection on port "+ String(FTP_CTRL_PORT));
+ #endif
+ cmdStatus = 2;
+ }
+ else if( cmdStatus == 2 ) // Ftp server idle
+ {
+
+ if( client.connected() ) // A client connected
+ {
+ clientConnected();
+ millisEndConnection = millis() + 10 * 1000 ; // wait client id during 10 s.
+ cmdStatus = 3;
+ }
+ }
+ else if( readChar() > 0 ) // got response
+ {
+ if( cmdStatus == 3 ) // Ftp server waiting for user identity
+ if( userIdentity() )
+ cmdStatus = 4;
+ else
+ cmdStatus = 0;
+ else if( cmdStatus == 4 ) // Ftp server waiting for user registration
+ if( userPassword() )
+ {
+ cmdStatus = 5;
+ millisEndConnection = millis() + millisTimeOut;
+ }
+ else
+ cmdStatus = 0;
+ else if( cmdStatus == 5 ) // Ftp server waiting for user command
+ if( ! processCommand())
+ cmdStatus = 0;
+ else
+ millisEndConnection = millis() + millisTimeOut;
+ }
+ else if (!client.connected() || !client)
+ {
+ cmdStatus = 1;
+ #ifdef FTP_DEBUG
+ Serial.println("client disconnected");
+ #endif
+ }
+
+ if( transferStatus == 1 ) // Retrieve data
+ {
+ if( ! doRetrieve())
+ transferStatus = 0;
+ }
+ else if( transferStatus == 2 ) // Store data
+ {
+ if( ! doStore())
+ transferStatus = 0;
+ }
+ else if( cmdStatus > 2 && ! ((int32_t) ( millisEndConnection - millis() ) > 0 ))
+ {
+ client.println("530 Timeout");
+ millisDelay = millis() + 200; // delay of 200 ms
+ cmdStatus = 0;
+ } else {
+ count_ftp2++;
+ }
+}
+
+void FtpServer::clientConnected()
+{
+ #ifdef FTP_DEBUG
+ Serial.println("Client connected!");
+ #endif
+ client.println( "220--- Welcome to FTP for ESP8266 ---");
+ client.println( "220--- By David Paiva ---");
+ client.println( "220 -- Version "+ String(FTP_SERVER_VERSION) +" --");
+ iCL = 0;
+}
+
+void FtpServer::disconnectClient()
+{
+ #ifdef FTP_DEBUG
+ Serial.println(" Disconnecting client");
+ #endif
+ abortTransfer();
+ client.println("221 Goodbye");
+ client.stop();
+}
+
+boolean FtpServer::userIdentity()
+{
+ if( strcmp( command, "USER" ))
+ client.println( "500 Syntax error");
+ if( strcmp( parameters, _FTP_USER.c_str() ))
+ client.println( "530 user not found");
+ else
+ {
+ client.println( "331 OK. Password required");
+ strcpy( cwdName, "/" );
+ return true;
+ }
+ millisDelay = millis() + 100; // delay of 100 ms
+ return false;
+}
+
+boolean FtpServer::userPassword()
+{
+ if( strcmp( command, "PASS" ))
+ client.println( "500 Syntax error");
+ else if( strcmp( parameters, _FTP_PASS.c_str() ))
+ client.println( "530 ");
+ else
+ {
+ #ifdef FTP_DEBUG
+ Serial.println( "OK. Waiting for commands.");
+ #endif
+ client.println( "230 OK.");
+ return true;
+ }
+ millisDelay = millis() + 100; // delay of 100 ms
+ return false;
+}
+
+boolean FtpServer::processCommand()
+{
+ ///////////////////////////////////////
+ // //
+ // ACCESS CONTROL COMMANDS //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // CDUP - Change to Parent Directory
+ //
+ if( ! strcmp( command, "CDUP" ) || ( ! strcmp( command, "CWD" ) && ! strcmp( parameters, ".." )))
+ {
+ bool ok = false;
+ if( strlen( cwdName ) > 1 ) // do nothing if cwdName is root
+ {
+ // if cwdName ends with '/', remove it (must not append)
+ if( cwdName[ strlen( cwdName ) - 1 ] == '/' )
+ cwdName[ strlen( cwdName ) - 1 ] = 0;
+ // search last '/'
+ char * pSep = strrchr( cwdName, '/' );
+ ok = pSep > cwdName;
+ // if found, ends the string on its position
+ if( ok )
+ {
+ * pSep = 0;
+ ok = SD_MMC.exists( cwdName );
+ }
+ }
+ // if an error appends, move to root
+ if( ! ok )
+ strcpy( cwdName, "/" );
+ // client << F("250 Ok. Current directory is ") << cwdName << eol;
+
+ client.println("250 Ok. Current directory is " + String(cwdName));
+ }
+ //
+ // CWD - Change Working Directory
+ //
+ else if( ! strcmp( command, "CWD" ))
+ {
+
+
+ char path[ FTP_CWD_SIZE ];
+ if( haveParameter() && makeExistsPath( path ))
+ {
+ strcpy( cwdName, path );
+ client.println( "250 Ok. Current directory is " + String(cwdName) );
+ }
+
+
+ //Serial.print("********************************************cwdName: ");Serial.println(String(cwdName));
+ /*
+ char path[ FTP_CWD_SIZE ];
+ if( strcmp( parameters, "." ) == 0 ){ // 'CWD .' is the same as PWD command
+
+ client.println( "257 \"" + String(cwdName) + "\" is your current directory");
+ */
+ // Serial.print("********************************************cwdName: ");Serial.println(String(cwdName));
+ /*
+ }
+ else
+ {
+ if( haveParameter() && makeExistsPath( path )){
+ strcpy( cwdName, path );
+ Serial.print("************************parameters: ");Serial.println(parameters);
+
+ client.println( "250 Ok. Current directory is " + String(cwdName) );
+ Serial.print("********************************************cwdName: ");Serial.println(String(cwdName));
+ }
+ }
+ */
+ }
+ //
+ // PWD - Print Directory
+ //
+ else if( ! strcmp( command, "PWD" ))
+ client.println( "257 \"" + String(cwdName) + "\" is your current directory");
+ //
+ // QUIT
+ //
+ else if( ! strcmp( command, "QUIT" ))
+ {
+ disconnectClient();
+ return false;
+ }
+
+ ///////////////////////////////////////
+ // //
+ // TRANSFER PARAMETER COMMANDS //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // MODE - Transfer Mode
+ //
+ else if( ! strcmp( command, "MODE" ))
+ {
+ if( ! strcmp( parameters, "S" ))
+ client.println( "200 S Ok");
+ // else if( ! strcmp( parameters, "B" ))
+ // client.println( "200 B Ok\r\n";
+ else
+ client.println( "504 Only S(tream) is suported");
+ }
+ //
+ // PASV - Passive Connection management
+ //
+ else if( ! strcmp( command, "PASV" ))
+ {
+ if (data.connected()) data.stop();
+ //dataServer.begin();
+ //dataIp = Ethernet.localIP();
+ dataIp = WiFi.localIP();
+ dataPort = FTP_DATA_PORT_PASV;
+ //data.connect( dataIp, dataPort );
+ //data = dataServer.available();
+ #ifdef FTP_DEBUG
+ Serial.println("Connection management set to passive");
+ Serial.println( "Data port set to " + String(dataPort));
+ #endif
+ client.println( "227 Entering Passive Mode ("+ String(dataIp[0]) + "," + String(dataIp[1])+","+ String(dataIp[2])+","+ String(dataIp[3])+","+String( dataPort >> 8 ) +","+String ( dataPort & 255 )+").");
+ dataPassiveConn = true;
+ }
+ //
+ // PORT - Data Port
+ //
+ else if( ! strcmp( command, "PORT" ))
+ {
+ if (data) data.stop();
+ // get IP of data client
+ dataIp[ 0 ] = atoi( parameters );
+ char * p = strchr( parameters, ',' );
+ for( uint8_t i = 1; i < 4; i ++ )
+ {
+ dataIp[ i ] = atoi( ++ p );
+ p = strchr( p, ',' );
+ }
+ // get port of data client
+ dataPort = 256 * atoi( ++ p );
+ p = strchr( p, ',' );
+ dataPort += atoi( ++ p );
+ if( p == NULL )
+ client.println( "501 Can't interpret parameters");
+ else
+ {
+
+ client.println("200 PORT command successful");
+ dataPassiveConn = false;
+ }
+ }
+ //
+ // STRU - File Structure
+ //
+ else if( ! strcmp( command, "STRU" ))
+ {
+ if( ! strcmp( parameters, "F" ))
+ client.println( "200 F Ok");
+ // else if( ! strcmp( parameters, "R" ))
+ // client.println( "200 B Ok\r\n";
+ else
+ client.println( "504 Only F(ile) is suported");
+ }
+ //
+ // TYPE - Data Type
+ //
+ else if( ! strcmp( command, "TYPE" ))
+ {
+ if( ! strcmp( parameters, "A" ))
+ client.println( "200 TYPE is now ASII");
+ else if( ! strcmp( parameters, "I" ))
+ client.println( "200 TYPE is now 8-bit binary");
+ else
+ client.println( "504 Unknow TYPE");
+ }
+
+ ///////////////////////////////////////
+ // //
+ // FTP SERVICE COMMANDS //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // ABOR - Abort
+ //
+ else if( ! strcmp( command, "ABOR" ))
+ {
+ abortTransfer();
+ client.println( "226 Data connection closed");
+ }
+ //
+ // DELE - Delete a File
+ //
+ else if( ! strcmp( command, "DELE" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ if( strlen( parameters ) == 0 )
+ client.println( "501 No file name");
+ else if( makePath( path ))
+ {
+ if( ! SD_MMC.exists( path ))
+ client.println( "550 File " + String(parameters) + " not found");
+ else
+ {
+ if( SD_MMC.remove( path ))
+ client.println( "250 Deleted " + String(parameters) );
+ else
+ client.println( "450 Can't delete " + String(parameters));
+ }
+ }
+ }
+ //
+ // LIST - List
+ //
+
+
+
+ else if( ! strcmp( command, "LIST" ))
+ {
+ if(dataConnect()){
+ client.println( "150 Accepted data connection");
+ uint16_t nm = 0;
+ File dir=SD_MMC.open(cwdName);
+ if((!dir)||(!dir.isDirectory()))
+ client.println( "550 Can't open directory " + String(cwdName) );
+ else
+ {
+ File file = dir.openNextFile();
+ while( file == 1)
+ {
+ String fn, fs;
+ fn = file.name();
+ int i = fn.lastIndexOf("/")+1;
+ fn.remove(0, i);
+ #ifdef FTP_DEBUG
+ Serial.println("File Name = "+ fn);
+ #endif
+ fs = String(file.size());
+
+ /* jz feb2020 code from https://github.com/espressif/arduino-esp32/blob/master/libraries/SD_MMC/examples/SDMMC_time/SDMMC_time.ino
+ * to implement file dates and times for the esp32 ftp
+
+
+ Serial.print(" FILE: ");
+ Serial.print(file.name());
+ Serial.print(" SIZE: ");
+ Serial.print(file.size());
+ 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);
+
+ */
+
+ time_t t= file.getLastWrite(); //jz
+ //struct tm * tmstruct = gmtime(&t); //jz
+ struct tm * tmstruct = localtime(&t); //jz
+
+ if(file.isDirectory()){
+ // jz start
+ char the_date[26];
+ sprintf(the_date, "%02d-%02d-%04d %02d:%02dAM ",( tmstruct->tm_mon)+1, tmstruct->tm_mday, (tmstruct->tm_year)+1900,tmstruct->tm_hour, tmstruct->tm_min);
+ data.println(the_date + fn);
+ //jz end
+
+ //jz data.println( "01-01-2000 00:00AM " + fn);
+ //Serial.println( "01-01-2000 00:00AM " + fn);
+ } else {
+ // jz start
+ char the_date[26];
+ sprintf(the_date, "%02d-%02d-%04d %02d:%02d ",( tmstruct->tm_mon)+1, tmstruct->tm_mday, (tmstruct->tm_year)+1900,tmstruct->tm_hour, tmstruct->tm_min);
+ data.println(the_date + fs + " " + fn);
+ //jz end
+
+ //jz data.println( "01-01-2000 00:00AM " + fs + " " + fn);
+ //Serial.println( "01-01-2000 00:00AM " + fs + " " + fn);
+// data.println( " " + fn );
+ }
+ nm ++;
+ //Serial.println("... opening next");
+ file = dir.openNextFile();
+ //Serial.println(file);
+ if (file < 1 ) {
+ //Serial.println("BREAK!");
+ break;
+
+ }
+ }
+ client.println( "226 " + String(nm) + " matches total");
+ Serial.println( "226 " + String(nm) + " matches total");
+ data.stop(); //jz aug2019
+ }
+
+ }
+ else{
+ client.println( "425 No data connection");
+ data.stop();
+ }
+ /*
+
+ if( ! dataConnect())
+ client.println( "425 No data connection");
+ else
+ {
+ client.println( "150 Accepted data connection");
+ uint16_t nm = 0;
+ File dir=SD_MMC.open(cwdName);
+ if((!dir)||(!dir.isDirectory()))
+ client.println( "550 Can't open directory " + String(cwdName) );
+ else
+ {
+ File file = dir.openNextFile();
+ while( file)
+ {
+ String fn, fs;
+ fn = file.name();
+ int i = fn.lastIndexOf("/")+1;
+ fn.remove(0, i);
+ #ifdef FTP_DEBUG
+ Serial.println("File Name = "+ fn);
+ #endif
+ fs = String(file.size());
+ if(file.isDirectory()){
+ data.println( "01-01-2000 00:00AM " + fn);
+ } else {
+ data.println( "01-01-2000 00:00AM " + fs + " " + fn);
+// data.println( " " + fn );
+ }
+ nm ++;
+ file = dir.openNextFile();
+ }
+ client.println( "226 " + String(nm) + " matches total");
+ }
+ data.stop();
+ }
+*/
+
+ }
+
+ //
+ // MLSD - Listing for Machine Processing (see RFC 3659)
+ //
+ else if( ! strcmp( command, "MLSD" ))
+ {
+ if( ! dataConnect())
+ client.println( "425 No data connection MLSD");
+ else
+ {
+ client.println( "150 Accepted data connection");
+ uint16_t nm = 0;
+// Dir dir= SD.openDir(cwdName);
+ File dir= SD_MMC.open(cwdName);
+ char dtStr[ 15 ];
+ // if(!SD.exists(cwdName))
+ if((!dir)||(!dir.isDirectory()))
+ client.println( "550 Can't open directory " +String(cwdName) );
+// client.println( "550 Can't open directory " +String(parameters) );
+ else
+ {
+// while( dir.next())
+ File file = dir.openNextFile();
+// while( dir.openNextFile())
+ while( file)
+ {
+
+ String fn,fs;
+ fn = file.name();
+ int pos = fn.lastIndexOf("/"); //ищем начало файла по последнему "/"
+ fn.remove(0, pos+1); //Удаляем все до имени файла включительно
+ fs = String(file.size());
+ if(file.isDirectory()){
+
+ data.println(fn);
+// data.println( "Type=dir;Size=" + fs + ";"+"modify=20000101000000;" +" " + fn);
+// data.println( "Type=dir;modify=20000101000000; " + fn);
+ } else {
+ data.println( fs + " " + fn);
+ //data.println( "Type=file;Size=" + fs + ";"+"modify=20000101160656;" +" " + fn);
+ //data.println( "Type=file;Size=" + fs + ";"+"modify=20000101000000;" +" " + fn);
+
+ }
+ nm ++;
+ file = dir.openNextFile();
+ }
+ client.println( "226-options: -a -l");
+ client.println( "226 " + String(nm) + " matches total");
+ }
+ data.stop();
+ }
+ }
+ //
+ // NLST - Name List
+ //
+ else if( ! strcmp( command, "NLST" ))
+ {
+ if( ! dataConnect())
+ client.println( "425 No data connection");
+ else
+ {
+ client.println( "150 Accepted data connection");
+ uint16_t nm = 0;
+// Dir dir=SD.openDir(cwdName);
+ File dir= SD_MMC.open(cwdName);
+ if( !SD_MMC.exists( cwdName ))
+ client.println( "550 Can't open directory " + String(parameters));
+ else
+ {
+ File file = dir.openNextFile();
+// while( dir.next())
+ while( file)
+ {
+// data.println( dir.fileName());
+ data.println( file.name());
+ nm ++;
+ file = dir.openNextFile();
+ }
+ client.println( "226 " + String(nm) + " matches total");
+ }
+ data.stop();
+ }
+ }
+ //
+ // NOOP
+ //
+ else if( ! strcmp( command, "NOOP" ))
+ {
+ // dataPort = 0;
+ client.println( "200 Zzz...");
+ }
+ //
+ // RETR - Retrieve
+ //
+ else if( ! strcmp( command, "RETR" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ if( strlen( parameters ) == 0 )
+ client.println( "501 No file name");
+ else if( makePath( path ))
+ {
+ file = SD_MMC.open(path, "r");
+ //Serial.println("open the file");
+ if( !file) {
+ client.println( "550 File " +String(parameters)+ " not found");
+ Serial.println("550");
+ }
+ else if( !file ) {
+ client.println( "450 Can't open " +String(parameters));
+ Serial.println("450");
+ }
+ else if( ! dataConnect()) {
+ client.println( "425 No data connection");
+ Serial.println("425");
+
+ }
+ else
+ {
+ #ifdef FTP_DEBUG
+ Serial.println("Sending " + String(parameters));
+ #endif
+ client.println( "150-Connected to port "+ String(dataPort));
+ client.println( "150 " + String(file.size()) + " bytes to download");
+ millisBeginTrans = millis();
+ bytesTransfered = 0;
+ transferStatus = 1;
+ }
+ }
+ }
+ //
+ // STOR - Store
+ //
+ else if( ! strcmp( command, "STOR" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ if( strlen( parameters ) == 0 )
+ client.println( "501 No file name");
+ else if( makePath( path ))
+ {
+ file = SD_MMC.open(path, "w");
+ if( !file)
+ client.println( "451 Can't open/create " +String(parameters) );
+ else if( ! dataConnect())
+ {
+ client.println( "425 No data connection");
+ file.close();
+ }
+ else
+ {
+ #ifdef FTP_DEBUG
+ Serial.println( "Receiving " +String(parameters));
+ #endif
+ client.println( "150 Connected to port " + String(dataPort));
+ millisBeginTrans = millis();
+ bytesTransfered = 0;
+ transferStatus = 2;
+ }
+ }
+ }
+ //
+ // MKD - Make Directory
+ //
+
+ else if( ! strcmp( command, "MKD" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ if( haveParameter() && makePath( path )){
+ if (SD_MMC.exists( path )){
+ client.println( "521 Can't create \"" + String(parameters) + ", Directory exists");
+ }
+ else
+ {
+ if( SD_MMC.mkdir( path )){
+ client.println( "257 \"" + String(parameters) + "\" created");
+ }
+ else{
+ client.println( "550 Can't create \"" + String(parameters));
+ }
+ }
+
+ }
+
+ }
+ //
+ // RMD - Remove a Directory
+ //
+ else if( ! strcmp( command, "RMD" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ if( haveParameter() && makePath( path )){
+ if( SD_MMC.rmdir( path )){
+ #ifdef FTP_DEBUG
+ Serial.println( " Deleting " +String(parameters));
+
+ #endif
+ client.println( "250 \"" + String(parameters) + "\" deleted");
+
+ }
+ else
+ {
+ client.println( "550 Can't remove \"" + String(parameters) + "\". Directory not empty?");
+ }
+ }
+
+ }
+ //
+ // RNFR - Rename From
+ //
+ else if( ! strcmp( command, "RNFR" ))
+ {
+ buf[ 0 ] = 0;
+ if( strlen( parameters ) == 0 )
+ client.println( "501 No file name");
+ else if( makePath( buf ))
+ {
+ if( ! SD_MMC.exists( buf ))
+ client.println( "550 File " +String(parameters)+ " not found");
+ else
+ {
+ #ifdef FTP_DEBUG
+ Serial.println("Renaming " + String(buf));
+ #endif
+ client.println( "350 RNFR accepted - file exists, ready for destination");
+ rnfrCmd = true;
+ }
+ }
+ }
+ //
+ // RNTO - Rename To
+ //
+ else if( ! strcmp( command, "RNTO" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ char dir[ FTP_FIL_SIZE ];
+ if( strlen( buf ) == 0 || ! rnfrCmd )
+ client.println( "503 Need RNFR before RNTO");
+ else if( strlen( parameters ) == 0 )
+ client.println( "501 No file name");
+ else if( makePath( path ))
+ {
+ if( SD_MMC.exists( path ))
+ client.println( "553 " +String(parameters)+ " already exists");
+ else
+ {
+ #ifdef FTP_DEBUG
+ Serial.println("Renaming " + String(buf) + " to " + String(path));
+ #endif
+ if( SD_MMC.rename( buf, path ))
+ client.println( "250 File successfully renamed or moved");
+ else
+ client.println( "451 Rename/move failure");
+
+ }
+ }
+ rnfrCmd = false;
+ }
+
+ ///////////////////////////////////////
+ // //
+ // EXTENSIONS COMMANDS (RFC 3659) //
+ // //
+ ///////////////////////////////////////
+
+ //
+ // FEAT - New Features
+ //
+ else if( ! strcmp( command, "FEAT" ))
+ {
+ client.println( "500 Unknow command");
+ //client.println( "211-Extensions suported:"); // recommendation by gendron for WinSCP - jz sep0519
+ //client.println( " MLSD");
+ //client.println( "211 End.");
+ }
+ //
+ // MDTM - File Modification Time (see RFC 3659)
+ //
+ else if (!strcmp(command, "MDTM"))
+ {
+ client.println("550 Unable to retrieve time");
+ }
+
+ //
+ // SIZE - Size of the file
+ //
+ else if( ! strcmp( command, "SIZE" ))
+ {
+ char path[ FTP_CWD_SIZE ];
+ if( strlen( parameters ) == 0 )
+ client.println( "501 No file name");
+ else if( makePath( path ))
+ {
+ file = SD_MMC.open(path, "r");
+ if(!file)
+ client.println( "450 Can't open " +String(parameters) );
+ else
+ {
+ client.println( "213 " + String(file.size()));
+ file.close();
+ }
+ }
+ }
+ //
+ // SITE - System command
+ //
+ else if( ! strcmp( command, "SITE" ))
+ {
+ client.println( "500 Unknow SITE command " +String(parameters) );
+ }
+ //
+ // OPTS - OPTS UTF8 ON - jz sep152019 - handle this by doing nothing
+ // - didnt work - windows 10 ftp sends command before login
+ //
+ else if( ! strcmp( command, "OPTS" ))
+ {
+ client.println( "200 Zzz...");
+ }
+ //
+ // Unrecognized commands ...
+ //
+ else
+ client.println( "500 Unknow command");
+
+ return true;
+}
+
+boolean FtpServer::dataConnect()
+{
+ unsigned long startTime = millis();
+ //wait 5 seconds for a data connection
+ if (!data.connected())
+ {
+
+ while (!dataServer.hasClient() && millis() - startTime < 10000)
+
+
+// while (!dataServer.available() && millis() - startTime < 10000)
+ {
+ //Serial.println("start while");Serial.println("before yield");
+ //Serial.print("hasClient / available "); Serial.print(dataServer.hasClient()); Serial.print("/"); Serial.println(dataServer.available());
+
+// delay(100); //jz sep152019
+
+ yield();
+ if (dataServer.hasClient() == 1){
+ Serial.println("Break in dataConnect");
+ break;
+ }
+ }
+ if (dataServer.hasClient()) {
+// if (dataServer.available()) {
+// Serial.println("before stop");
+ data.stop();
+ data = dataServer.available();
+ #ifdef FTP_DEBUG
+ Serial.println("ftpdataserver client....");
+ #endif
+
+ }
+ }
+
+ return data.connected();
+
+}
+
+boolean FtpServer::doRetrieve()
+{
+ //Serial.println("doRetreive");
+if (data.connected())
+{
+ int16_t nb = file.readBytes(buf, FTP_BUF_SIZE);
+ if (nb > 0)
+ {
+ data.write((uint8_t*)buf, nb);
+ bytesTransfered += nb;
+ //Serial.print(" - ");Serial.print(bytesTransfered);
+ return true;
+ }
+}
+closeTransfer();
+return false;
+}
+
+
+boolean FtpServer::doStore()
+{
+ if( data.connected() )
+ {
+ int16_t nb = data.readBytes((uint8_t*) buf, FTP_BUF_SIZE );
+ if( nb > 0 )
+ {
+ // Serial.println( millis() << " " << nb << endl;
+ file.write((uint8_t*) buf, nb );
+ bytesTransfered += nb;
+ }
+ return true;
+ }
+ closeTransfer();
+ return false;
+}
+
+void FtpServer::closeTransfer()
+{
+ uint32_t deltaT = (int32_t) ( millis() - millisBeginTrans );
+ if( deltaT > 0 && bytesTransfered > 0 )
+ {
+ client.println( "226-File successfully transferred");
+ client.println( "226 " + String(deltaT) + " ms, "+ String(bytesTransfered / deltaT) + " kbytes/s");
+ }
+ else
+ client.println( "226 File successfully transferred");
+
+ file.close();
+ data.stop();
+}
+
+void FtpServer::abortTransfer()
+{
+ if( transferStatus > 0 )
+ {
+ file.close();
+ data.stop();
+ client.println( "426 Transfer aborted" );
+ #ifdef FTP_DEBUG
+ Serial.println( "Transfer aborted!") ;
+ #endif
+ }
+ transferStatus = 0;
+}
+
+// Read a char from client connected to ftp server
+//
+// update cmdLine and command buffers, iCL and parameters pointers
+//
+// return:
+// -2 if buffer cmdLine is full
+// -1 if line not completed
+// 0 if empty line received
+// length of cmdLine (positive) if no empty line received
+
+int8_t FtpServer::readChar()
+{
+ int8_t rc = -1;
+
+ if( client.available())
+ {
+ char c = client.read();
+ // char c;
+ // client.readBytes((uint8_t*) c, 1);
+ #ifdef FTP_DEBUG
+ Serial.print( c);
+ #endif
+ if( c == '\\' )
+ c = '/';
+ if( c != '\r' )
+ if( c != '\n' )
+ {
+ if( iCL < FTP_CMD_SIZE )
+ cmdLine[ iCL ++ ] = c;
+ else
+ rc = -2; // Line too long
+ }
+ else
+ {
+ cmdLine[ iCL ] = 0;
+ command[ 0 ] = 0;
+ parameters = NULL;
+ // empty line?
+ if( iCL == 0 )
+ rc = 0;
+ else
+ {
+ rc = iCL;
+ // search for space between command and parameters
+ parameters = strchr( cmdLine, ' ' );
+ if( parameters != NULL )
+ {
+ if( parameters - cmdLine > 4 )
+ rc = -2; // Syntax error
+ else
+ {
+ strncpy( command, cmdLine, parameters - cmdLine );
+ command[ parameters - cmdLine ] = 0;
+
+ while( * ( ++ parameters ) == ' ' )
+ ;
+ }
+ }
+ else if( strlen( cmdLine ) > 4 )
+ rc = -2; // Syntax error.
+ else
+ strcpy( command, cmdLine );
+ iCL = 0;
+ }
+ }
+ if( rc > 0 )
+ for( uint8_t i = 0 ; i < strlen( command ); i ++ )
+ command[ i ] = toupper( command[ i ] );
+ if( rc == -2 )
+ {
+ iCL = 0;
+ client.println( "500 Syntax error");
+ }
+ }
+ return rc;
+}
+
+// Make complete path/name from cwdName and parameters
+//
+// 3 possible cases: parameters can be absolute path, relative path or only the name
+//
+// parameters:
+// fullName : where to store the path/name
+//
+// return:
+// true, if done
+
+boolean FtpServer::makePath( char * fullName )
+{
+ return makePath( fullName, parameters );
+}
+
+boolean FtpServer::makePath( char * fullName, char * param )
+{
+ if( param == NULL )
+ param = parameters;
+
+ // Root or empty?
+ if( strcmp( param, "/" ) == 0 || strlen( param ) == 0 )
+ {
+ strcpy( fullName, "/" );
+ return true;
+ }
+ // If relative path, concatenate with current dir
+ if( param[0] != '/' )
+ {
+ strcpy( fullName, cwdName );
+ if( fullName[ strlen( fullName ) - 1 ] != '/' )
+ strncat( fullName, "/", FTP_CWD_SIZE );
+ strncat( fullName, param, FTP_CWD_SIZE );
+ }
+ else
+ strcpy( fullName, param );
+ // If ends with '/', remove it
+ uint16_t strl = strlen( fullName ) - 1;
+ if( fullName[ strl ] == '/' && strl > 1 )
+ fullName[ strl ] = 0;
+ if( strlen( fullName ) < FTP_CWD_SIZE )
+ return true;
+
+ client.println( "500 Command line too long");
+ return false;
+}
+
+// Calculate year, month, day, hour, minute and second
+// from first parameter sent by MDTM command (YYYYMMDDHHMMSS)
+//
+// parameters:
+// pyear, pmonth, pday, phour, pminute and psecond: pointer of
+// variables where to store data
+//
+// return:
+// 0 if parameter is not YYYYMMDDHHMMSS
+// length of parameter + space
+
+uint8_t FtpServer::getDateTime( uint16_t * pyear, uint8_t * pmonth, uint8_t * pday,
+ uint8_t * phour, uint8_t * pminute, uint8_t * psecond )
+{
+ char dt[ 15 ];
+
+ // Date/time are expressed as a 14 digits long string
+ // terminated by a space and followed by name of file
+ if( strlen( parameters ) < 15 || parameters[ 14 ] != ' ' )
+ return 0;
+ for( uint8_t i = 0; i < 14; i++ )
+ if( ! isdigit( parameters[ i ]))
+ return 0;
+
+ strncpy( dt, parameters, 14 );
+ dt[ 14 ] = 0;
+ * psecond = atoi( dt + 12 );
+ dt[ 12 ] = 0;
+ * pminute = atoi( dt + 10 );
+ dt[ 10 ] = 0;
+ * phour = atoi( dt + 8 );
+ dt[ 8 ] = 0;
+ * pday = atoi( dt + 6 );
+ dt[ 6 ] = 0 ;
+ * pmonth = atoi( dt + 4 );
+ dt[ 4 ] = 0 ;
+ * pyear = atoi( dt );
+ return 15;
+}
+
+// Create string YYYYMMDDHHMMSS from date and time
+//
+// parameters:
+// date, time
+// tstr: where to store the string. Must be at least 15 characters long
+//
+// return:
+// pointer to tstr
+
+char * FtpServer::makeDateTimeStr( char * tstr, uint16_t date, uint16_t time )
+{
+ sprintf( tstr, "%04u%02u%02u%02u%02u%02u",
+ (( date & 0xFE00 ) >> 9 ) + 1980, ( date & 0x01E0 ) >> 5, date & 0x001F,
+ ( time & 0xF800 ) >> 11, ( time & 0x07E0 ) >> 5, ( time & 0x001F ) << 1 );
+ return tstr;
+}
+
+bool FtpServer::haveParameter()
+{
+ if( parameters != NULL && strlen( parameters ) > 0 )
+ return true;
+ client.println ("501 No file name");
+ return false;
+}
+bool FtpServer::makeExistsPath( char * path, char * param )
+{
+ if( ! makePath( path, param ))
+ return false;
+ if( SD_MMC.exists( path ))
+ return true;
+ client.println("550 " + String(path) + " not found.");
+
+ return false;
+}
diff --git a/v99/ESP32FtpServer.h b/v99/ESP32FtpServer.h
new file mode 100644
index 0000000..23869e5
--- /dev/null
+++ b/v99/ESP32FtpServer.h
@@ -0,0 +1,116 @@
+
+/*
+* FTP SERVER FOR ESP8266
+ * based on FTP Serveur for Arduino Due and Ethernet shield (W5100) or WIZ820io (W5200)
+ * based on Jean-Michel Gallego's work
+ * modified to work with esp8266 SPIFFS by David Paiva (david@nailbuster.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+// 2017: modified by @robo8080
+// 2019: modified by @fa1ke5
+
+/*******************************************************************************
+ ** **
+ ** DEFINITIONS FOR FTP SERVER **
+ ** **
+ *******************************************************************************/
+
+// Uncomment to print debugging info to console attached to ESP8266
+//#define FTP_DEBUG
+
+#ifndef FTP_SERVERESP_H
+#define FTP_SERVERESP_H
+
+//#include "Streaming.h"
+#include "SD_MMC.h"
+#include
+#include
+
+#define FTP_SERVER_VERSION "FTP-2016-01-14"
+
+#define FTP_CTRL_PORT 21 // Command port on wich server is listening
+#define FTP_DATA_PORT_PASV 50009 // Data port in passive mode
+
+#define FTP_TIME_OUT 5 // Disconnect client after 5 minutes of inactivity
+#define FTP_CMD_SIZE 255 + 8 // max size of a command
+#define FTP_CWD_SIZE 255 + 8 // max size of a directory name
+#define FTP_FIL_SIZE 255 // max size of a file name
+
+//#define FTP_BUF_SIZE 512 //512 // size of file buffer for read/write
+//#define FTP_BUF_SIZE 2*1460 //512 // size of file buffer for read/write
+//#define FTP_BUF_SIZE 4096 //512 // 700 KByte/s download in AP mode, direct connection.
+//#define FTP_BUF_SIZE 8192 reduce in v82
+//#define FTP_BUF_SIZE 2048
+
+#define FTP_BUF_SIZE 4096
+
+
+class FtpServer
+{
+public:
+ void begin(String uname, String pword);
+ void handleFTP();
+
+private:
+ bool haveParameter();
+bool makeExistsPath( char * path, char * param = NULL );
+ void iniVariables();
+ void clientConnected();
+ void disconnectClient();
+ boolean userIdentity();
+ boolean userPassword();
+ boolean processCommand();
+ boolean dataConnect();
+ boolean doRetrieve();
+ boolean doStore();
+ void closeTransfer();
+ void abortTransfer();
+ boolean makePath( char * fullname );
+ boolean makePath( char * fullName, char * param );
+ uint8_t getDateTime( uint16_t * pyear, uint8_t * pmonth, uint8_t * pday,
+ uint8_t * phour, uint8_t * pminute, uint8_t * second );
+ char * makeDateTimeStr( char * tstr, uint16_t date, uint16_t time );
+ int8_t readChar();
+
+ IPAddress dataIp; // IP address of client for data
+ WiFiClient client;
+ WiFiClient data;
+
+ File file;
+
+ boolean dataPassiveConn;
+ uint16_t dataPort;
+ char buf[ FTP_BUF_SIZE ]; // data buffer for transfers
+ char cmdLine[ FTP_CMD_SIZE ]; // where to store incoming char from client
+ char cwdName[ FTP_CWD_SIZE ]; // name of current directory
+ char command[ 5 ]; // command sent by client
+ boolean rnfrCmd; // previous command was RNFR
+ char * parameters; // point to begin of parameters sent by client
+ uint16_t iCL; // pointer to cmdLine next incoming char
+ int8_t cmdStatus, // status of ftp command connexion
+ transferStatus; // status of ftp data transfer
+ uint32_t millisTimeOut, // disconnect after 5 min of inactivity
+ millisDelay,
+ millisEndConnection, //
+ millisBeginTrans, // store time of beginning of a transaction
+ bytesTransfered; //
+ String _FTP_USER;
+ String _FTP_PASS;
+
+
+
+};
+
+#endif // FTP_SERVERESP_H
diff --git a/v99/TimeLapseAvi99x.ino b/v99/TimeLapseAvi99x.ino
new file mode 100644
index 0000000..35de88a
--- /dev/null
+++ b/v99/TimeLapseAvi99x.ino
@@ -0,0 +1,2798 @@
+#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 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 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\ESP32
+Using library EEPROM at version 1.0.3 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\EEPROM
+Using library WiFi at version 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\WiFi
+Using library WiFiManager at version 2.0.3-alpha in folder: C:\ArduinoPortable\arduino-1.8.13\portable\sketchbook\libraries\WiFiManager
+Using library WebServer at version 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\WebServer
+Using library DNSServer at version 1.1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\DNSServer
+Using library ESPmDNS at version 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\ESPmDNS
+Using library WiFiClientSecure at version 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\WiFiClientSecure
+Using library ArduinoJson at version 6.16.1 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\sketchbook\libraries\ArduinoJson
+Using library SD_MMC at version 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\SD_MMC
+Using library FS at version 1.0 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\FS
+Using library HTTPClient at version 1.2 in folder: C:\ArduinoPortable\arduino-1.8.13\portable\packages\esp32\hardware\esp32\1.0.6\libraries\HTTPClient
+"C:\\ArduinoPortable\\arduino-1.8.13\\portable\\packages\\esp32\\tools\\xtensa-esp32-elf-gcc\\1.22.0-97-gc752ad5-5.2.0/bin/xtensa-esp32-elf-size" -A "C:\\Users\\James\\AppData\\Local\\Temp\\arduino_build_955359/TimeLapseAvi99.ino.elf"
+Sketch uses 1295090 bytes (41%) of program storage space. Maximum is 3145728 bytes.
+Global variables use 58380 bytes (17%) of dynamic memory, leaving 269300 bytes for local variables. Maximum is 327680 bytes.
+
+*/
+
+
+static const char vernum[] = "v99";
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// 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[100];
+int send_a_telegram = 0;
+int Wait_for_bot = 0;
+
+#include
+
+#ifdef include_ftp
+#include "ESP32FtpServer.h"
+FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial
+#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, 0x39, 0x39, 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();
+ count_ftp++;
+ delay(1);
+
+ }
+}
+#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();
+ }
+ }
+ }
+ }
+}
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// get_touch5 - handler for capactive touch sensor - enable/disable pir
+//
+
+void get_touch5 () {
+ // capacitive touch sensor pin 12 == T5
+
+ //int x = (touchRead(T5) + touchRead(T5) + touchRead(T5) ) / 3;
+ int x = touchRead(T5);
+
+ //Serial.print("TOUCH Interupt>> "); Serial.println(x);
+
+ if ( x < 29 ) {
+
+ if (PIRenabled == 1 ) {
+ if (millis() - TouchDeBounce > 1000 ) {
+
+ PIRenabled = 0;
+ TouchDeBounce = millis();
+ Serial.println("\nPIR Disabled\n");
+ do_blink();
+
+ }
+ } else {
+ if (millis() - TouchDeBounce > 1000 ) {
+
+ PIRenabled = 1;
+ TouchDeBounce = millis();
+ Serial.println("PIR Enabled.");
+ PIRstatus = digitalRead(PIRpin) + digitalRead(PIRpin) + digitalRead(PIRpin) ;
+ if (PIRstatus == 3) {
+ 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);
+
+#ifndef get_rid_of_touch
+ touchAttachInterrupt(T5, get_touch5, 30);
+#endif
+ Serial.print("Touch T5 = ");
+ for (int i = 0; i < 5; i++) {
+ Serial.print( touchRead(T5) ); Serial.print(", ");
+ }
+ 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 esp_err_t 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 fname[130];
+
+ //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.jpg", 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.jpg", 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.jpg", 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.jpg", 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.jpg", strftime_buf2, devname, strftime_buf, quality, capture_interval, xlength, xspeed);
+ } else {
+ Serial.println("Wrong framesize");
+ }
+
+ FILE *file = fopen(fname, "w");
+ //file = fopen(fname, "w");
+ if (file != NULL) {
+ size_t err = fwrite(fb->buf, 1, fb->len, file);
+ Serial.printf("File saved: %s\n", fname);
+ } else {
+ Serial.println("Could not open file");
+ }
+ 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";
+
+ sprintf(the_page, msg, vernum, devname, localip, use, tot, rssi, strdate); //fname
+
+ 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
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// low_voltage_save - runs during brownout just before system brownout handler
+//
+// - deepsleeps rather than rebooting constantly with low battery or dodgy power supply
+//
+// - mostly included as information, as it was a lot of work and didn't ultimately work to close
+// the avi files
+// - you could use this to set a gpio to connect a new battery or something ... but you cannot prevent the low-power shurdown
+
+void IRAM_ATTR low_voltage_save(void *arg) {
+
+ Serial.println("\nJZ low voltage handler\nStarting at ");
+ long start_of_inter = millis();
+ Serial.println(start_of_inter);
+ Serial.println("------------------------------------");
+ time(&now);
+ const char *strdate = ctime(&now);
+ Serial.println(strdate);
+
+ Serial.print("low volt handler, core "); Serial.print(xPortGetCoreID());
+ Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL));
+
+ 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
+
+ print_ram();
+
+ //esp_cpu_stall ( !xPortGetCoreID () );
+
+ //vTaskDelay ( 200000 / portTICK_PERIOD_MS ); // does not work
+ //Serial.println("slept 200 seconds - does not work!");
+
+ //Serial.println("3 seconds to close files - does not work");
+ //delay(3000);
+
+ long last_millis = millis();
+ for (int i = 0; i < 1000000; i++) {
+ if (millis() - last_millis > 10 ) {
+ Serial.print(millis() - start_of_inter);
+ Serial.print(" ms, i = ");
+ Serial.println(i);
+ last_millis = millis();
+ }
+
+ //if ( millis() - start_of_inter > 250 && millis() - start_of_inter < 255) {
+ // Serial.println("250 ms passed - try to extend before 300ms wdt -- does not work");
+ // esp_task_wdt_reset();
+ //}
+ if ( millis() - start_of_inter > 280) {
+ Serial.println("280 ms passed - deepsleep ");
+
+ // pinMode(4, OUTPUT); // Blinding Disk-Avtive Light
+ // digitalWrite(4, HIGH); // turn ON
+ pinMode(4, OUTPUT); // turn off Blinding Disk-Active Light and keep it off during deepsleep
+ digitalWrite(4, LOW);
+ rtc_gpio_hold_en(GPIO_NUM_4);
+ gpio_deep_sleep_hold_en();
+ esp_deep_sleep_start();
+ }
+ }
+ //Serial.println("... switching to system shutdown ..."); // we never get here
+}
+
+#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
+//
+
+// You may have to edit rtc_cntl.h ... according to this link -- doesn't seem to be included in esp32 libraries as of Jun 2020
+// https://github.com/espressif/esp-idf/commit/17bd6e8faba15812780d21e6e3db08fb26dd7033#diff-5e22dcf9fc6087d1585c7b2e434c0932
+// https://github.com/espressif/esp-idf/pull/4532
+// C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\include\driver\driver -- approximate path
+// #include "driver/rtc_cntl.h"
+// ... or i'll just include it with this program
+#include "rtc_cntl.h"
+// v99 use rtc_cntl now included with 1.06
+// #include "driver/rtc_cntl.h"
+
+void setup() {
+
+ Serial.begin(115200);
+ Serial.println("\n\n---");
+ //Serial.println("delay 5 seconds"); delay(5000);
+
+ esp_err_t xx = rtc_isr_register(low_voltage_save, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M); // see 10 lines up if you get an error here!
+
+ 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) 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,
+ 0); // on cpu 0
+
+ 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 (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());
+ }
+ 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 ??
+
+ //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 esp_err_t 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 esp_err_t 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 esp_err_t 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 esp_err_t 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 esp_err_t 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/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();
+
+ 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
+
+
+
+ pir_enable
+ pir_disable
+ bot_enable
+ bot_disable
+
+
+
+
+
+
+
+
+)rawliteral";
+
+ //Serial.print(strlen(msg)); Serial.print(" ");
+
+ 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, 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 = 10;
+ //config.max_open_sockets = 2;
+ config.task_priority = 1;
+
+ //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/v99/UniversalTelegramBot.cpp b/v99/UniversalTelegramBot.cpp
new file mode 100644
index 0000000..5fd4b2d
--- /dev/null
+++ b/v99/UniversalTelegramBot.cpp
@@ -0,0 +1,952 @@
+/*
+ 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
+
+#include "UniversalTelegramBot.h"
+
+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
+ 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 = "";
+
+
+ //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();
+ }
+}
diff --git a/v99/UniversalTelegramBot.h b/v99/UniversalTelegramBot.h
new file mode 100644
index 0000000..c5220ee
--- /dev/null
+++ b/v99/UniversalTelegramBot.h
@@ -0,0 +1,125 @@
+/*
+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
+
+#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, 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;
+ Client *client;
+ void closeClient();
+ const int maxMessageLength = 1500; //was 1500
+ bool processResult(JsonObject result, int messageIndex);
+};
+
+#endif
diff --git a/v99/rtc_cntl.h b/v99/rtc_cntl.h
new file mode 100644
index 0000000..1fd303a
--- /dev/null
+++ b/v99/rtc_cntl.h
@@ -0,0 +1,64 @@
+// ... pending inclusion in the new esp32 distribution - jz
+
+// You may have to edit rtc_cntl.h ... according to this link -- doesn't seem to be included in esp32 libraries as of Jun 2020 ... or I'll just put it here
+// https://github.com/espressif/esp-idf/commit/17bd6e8faba15812780d21e6e3db08fb26dd7033#diff-5e22dcf9fc6087d1585c7b2e434c0932
+// https://github.com/espressif/esp-idf/pull/4532
+// C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\include\driver\driver -- approximate path
+
+
+// Copyright 2016-2017 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include
+#include "esp_err.h"
+#include "esp_intr_alloc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Register a handler for specific RTC_CNTL interrupts
+ *
+ * Multiple handlers can be registered using this function. Whenever an
+ * RTC interrupt happens, all handlers with matching rtc_intr_mask values
+ * will be called.
+ *
+ * @param handler handler function to call
+ * @param handler_arg argument to be passed to the handler
+ * @param rtc_intr_mask combination of RTC_CNTL_*_INT_ENA bits indicating the
+ * sources to call the handler for
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NO_MEM not enough memory to allocate handler structure
+ * - other errors returned by esp_intr_alloc
+ */
+esp_err_t rtc_isr_register(intr_handler_t handler, void* handler_arg,
+ uint32_t rtc_intr_mask);
+/**
+ * @brief Deregister the handler previously registered using rtc_isr_register
+ * @param handler handler function to call (as passed to rtc_isr_register)
+ * @param handler_arg argument of the handler (as passed to rtc_isr_register)
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if a handler matching both handler and
+ * handler_arg isn't registered
+ */
+esp_err_t rtc_isr_deregister(intr_handler_t handler, void* handler_arg);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/v99/settings.h b/v99/settings.h
new file mode 100644
index 0000000..5067818
--- /dev/null
+++ b/v99/settings.h
@@ -0,0 +1,66 @@
+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
+//#define TIMEZONE "MST7MDT,M3.2.0/2:00:00,M11.1.0/2:00:00" // mountain time
+
+// 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 = "jzjzjz";
+const char* password = "jzjzjz";
+
+// 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"