diff --git a/v23/ESP32FtpServer.cpp b/old/v23/ESP32FtpServer.cpp similarity index 96% rename from v23/ESP32FtpServer.cpp rename to old/v23/ESP32FtpServer.cpp index 6b7b19b..161c154 100644 --- a/v23/ESP32FtpServer.cpp +++ b/old/v23/ESP32FtpServer.cpp @@ -1,1129 +1,1129 @@ -/* - * 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" - - - -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; - } -} - -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()); - if(file.isDirectory()){ - data.println( "01-01-2000 00:00AM " + fn); - //Serial.println( "01-01-2000 00:00AM " + fn); - } else { - 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 - } - - } - 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( "211-Extensions suported:"); - 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) ); - } - // - // 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 - - 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; -} +/* + * 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" + + + +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; + } +} + +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()); + if(file.isDirectory()){ + data.println( "01-01-2000 00:00AM " + fn); + //Serial.println( "01-01-2000 00:00AM " + fn); + } else { + 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 + } + + } + 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( "211-Extensions suported:"); + 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) ); + } + // + // 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 + + 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/v60/ESP32FtpServer.h b/old/v23/ESP32FtpServer.h similarity index 97% rename from v60/ESP32FtpServer.h rename to old/v23/ESP32FtpServer.h index a40186f..c9c57e5 100644 --- a/v60/ESP32FtpServer.h +++ b/old/v23/ESP32FtpServer.h @@ -1,112 +1,112 @@ - -/* -* 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. - - -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 + +/* +* 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. + + +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/v23/README.md b/old/v23/README.md similarity index 100% rename from v23/README.md rename to old/v23/README.md diff --git a/v23/TimeLapseAvi23x.ino b/old/v23/TimeLapseAvi23x.ino similarity index 96% rename from v23/TimeLapseAvi23x.ino rename to old/v23/TimeLapseAvi23x.ino index 6ed23ba..0454081 100644 --- a/v23/TimeLapseAvi23x.ino +++ b/old/v23/TimeLapseAvi23x.ino @@ -1,1495 +1,1495 @@ -/* - - 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 - 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 - - Acknowlegements: - - 1. https://robotzero.one/time-lapse-esp32-cameras/ - Timelapse programs for ESP32-CAM version that sends snapshots of screen. - 2. https://github.com/nailbuster/esp8266FTPServer - ftp server (slightly modifed to get the directory function working) - 3. https://github.com/ArduCAM/Arduino/tree/master/ArduCAM/examples/mini - ArduCAM Mini demo (C)2017 LeeWeb: http://www.ArduCAM.com - I copied the structure of the avi file, some calculations. - - The is Arduino code, with standard setup for ESP32-CAM - - Board ESP32 Wrover Module - - Partition Scheme Huge APP (3MB No OTA) - - This program records an AVI video on the SD Card of an ESP32-CAM. - - It will record realtime video at limited framerates, or timelapses with the full resolution of the ESP32-CAM. - It is controlled by a web page it serves to stop and start recordings with many parameters, and look through the viewfinder. - - You can control framesize (UXGA, VGA, ...), quality, length, and fps to record, and fps to playback later, etc. - - There is also an ftp server to download the recordings to a PC. - - Instructions: - - The program uses a fixed IP of 192.168.1.222, so you can browse to it from your phone or computer. - - http://192.168.1.222/ -- this gives you the status of the recording in progress and lets you look through the viewfinder - - http://192.168.1.222/stop -- this stops the recording in progress and displays some sample commands to start new recordings - - ftp://192.168.1.222/ -- gives you the ftp server - - The ftp for esp32 seems to not be a full ftp. The Chrome Browser and the Windows command line ftp's did not work with this, but - the Raspbarian command line ftp works fine, and an old Windows ftp I have called CoffeeCup Free FTP also works, which is what I have been using. - You can download at about 450 KB/s -- which is better than having to retreive the SD chip if you camera is up in a tree! - - http://192.168.1.222/start?framesize=VGA&length=1800&interval=250&quality=10&repeat=100&speed=1&gray=0 -- this is a sample to start a new recording - - framesize can be UXGA, SVGA, VGA, CIF (default VGA) - length is length in seconds of the recording 0..3600 (default 1800) - interval is the milli-seconds between frames (default 200) - quality is a number 5..50 for the jpeg - smaller number is higher quality with bigger and more detailed jpeg (default 10) - repeat is a number of who many of the same recordings should be made (default 100) - speed is a factor to speed up realtime for a timelapse recording - 1 is realtime (default 1) - gray is 1 for a grayscale video (default 0 - color) - - These factors have to be within the limit of the SD chip to receive the data. - For example, using a LEXAR 300x 32GB microSDHC UHS-I, the following works for me: - - UXGA quality 10, 2 fps (or interval of 500ms) - SVGA quality 10, 5 fps (200ms) - VGA quality 10, 10 fps (100ms) - CIG quality 10, 20 fps (50ms) - - If you increase fps, you might have to reduce quality or framesize to keep it from dropping frames as it writes all the data to the SD chip. - - Also, other SD chips will be faster or slower. I was using a SanDisk 16GB microSDHC "Up to 653X" - which was slower and more unpredictable than the LEXAR ??? - - Search for "zzz" to find places to modify the code for: - 1. Your wifi name and password - 2. Your preferred ip address (with default gateway, etc) - 3. Your Timezone for use in filenames - 4. Defaults for framesize, quality, ... and if the recording should start on reboot of the ESP32 without receiving a command - -*/ - - - -//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE -#include "esp_log.h" -#include "esp_http_server.h" -#include "esp_camera.h" - -//#include // redundant - -#include "ESP32FtpServer.h" -#include - -FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial - - -// Time -#include "time.h" -#include "lwip/err.h" -#include "lwip/apps/sntp.h" - -// MicroSD -#include "driver/sdmmc_host.h" -#include "driver/sdmmc_defs.h" -#include "sdmmc_cmd.h" -#include "esp_vfs_fat.h" - -long current_millis; -long last_capture_millis = 0; -static esp_err_t cam_err; -static esp_err_t card_err; -char strftime_buf[64]; -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; - -// 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 - -// -// -// EDIT ssid and password -// -// zzz -const char* ssid = "ssid123"; -const char* password = "ssidpassword"; - -// these are just declarations -- look below to edit defaults - -int capture_interval = 200; // microseconds between captures -int total_frames = 300; // default updated below -int recording = 1; // default start recording on reboot -int framesize = 6; // vga -int repeat = 100; // capture 100 videos -int quality = 10; -int xspeed = 1; -int xlength = 3; -int gray = 0; - - -#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}; // -uint8_t svga_h[2] = {0x58, 0x02}; // -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, 0x67, - 0x6D, 0x61, 0x69, 0x6C, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69, -}; - - -// -// Writes an uint32_t in Big Endian at current file position -// -static void inline print_quartet(unsigned long i, FILE * fd) -{ - uint8_t x[1]; - - 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[3000]; - -char localip[20]; -WiFiEventId_t eventID; - -#include "soc/soc.h" -#include "soc/rtc_cntl_reg.h" - - -void setup() { - //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems - - Serial.begin(115200); - - Serial.setDebugOutput(true); - - // zzz - Serial.println(" "); - Serial.println("-------------------------------------"); - Serial.println("ESP-CAM Video Recorder v23"); - Serial.println(" ip 192.168.1.222 "); - Serial.println("-------------------------------------"); - - pinMode(33, OUTPUT); // little red led on back of chip - - digitalWrite(33, LOW); // turn on the red LED on the back of chip - - eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { - Serial.print("WiFi lost connection. Reason: "); - Serial.println(info.disconnected.reason); - - if (WiFi.status() == WL_CONNECTED) { - Serial.println("*** connected/disconnected issue! WiFi disconnected ???..."); - WiFi.disconnect(); - } else { - Serial.println("*** WiFi disconnected ???..."); - } - }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); - - - if (init_wifi()) { // Connected to WiFi - internet_connected = true; - Serial.println("Internet connected"); - init_time(); - time(&now); - //setenv("TZ", "GMT0BST,M3.5.0/01,M10.5.0/02", 1); - // zzz - setenv("TZ", "MST7MDT,M3.2.0/2:00:00,M11.1.0/2:00:00", 1); // mountain time zone - tzset(); - } - - camera_config_t config; - config.ledc_channel = LEDC_CHANNEL_0; - config.ledc_timer = LEDC_TIMER_0; - config.pin_d0 = Y2_GPIO_NUM; - config.pin_d1 = Y3_GPIO_NUM; - config.pin_d2 = Y4_GPIO_NUM; - config.pin_d3 = Y5_GPIO_NUM; - config.pin_d4 = Y6_GPIO_NUM; - config.pin_d5 = Y7_GPIO_NUM; - config.pin_d6 = Y8_GPIO_NUM; - config.pin_d7 = Y9_GPIO_NUM; - config.pin_xclk = XCLK_GPIO_NUM; - config.pin_pclk = PCLK_GPIO_NUM; - config.pin_vsync = VSYNC_GPIO_NUM; - config.pin_href = HREF_GPIO_NUM; - config.pin_sscb_sda = SIOD_GPIO_NUM; - config.pin_sscb_scl = SIOC_GPIO_NUM; - config.pin_pwdn = PWDN_GPIO_NUM; - config.pin_reset = RESET_GPIO_NUM; - config.xclk_freq_hz = 20000000; - config.pixel_format = PIXFORMAT_JPEG; - - //init with high specs to pre-allocate larger buffers - if (psramFound()) { - config.frame_size = FRAMESIZE_UXGA; - config.jpeg_quality = 1; - config.fb_count = 2; - } else { - config.frame_size = FRAMESIZE_SVGA; // svga 12 fails due to jpg 60000 - config.jpeg_quality = 12; - config.fb_count = 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(); - return; - } - - // 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; - } - - // 200 ms x 150 frames = 30 seconds is 3 MB indoors - // 200 ms x 300 frames = 1 minute is about 6MD indoor - // 20 ms x 3000 frames = 10 minute is 60 MB indoor - // burst 1000 frames gives 8 fps rather than 5, so 2 minutues 20 MB indoor - - startCameraServer(); - - Serial.print("Camera Ready! Use 'http://"); - Serial.print(WiFi.localIP()); - Serial.println("' to connect"); - - sprintf(localip, "%s", WiFi.localIP().toString().c_str()); - Serial.print("localip "); Serial.println(localip); - - // zzz username and password for ftp server - - ftpSrv.begin("esp", "esp"); - digitalWrite(33, HIGH); - - - // - // startup defaults -- EDIT HERE - // zzz - - sensor_t * s = esp_camera_sensor_get(); - s->set_framesize(s, FRAMESIZE_VGA); - s->set_quality(s, 10); - do_fb(); // do a couple captures to make sure camera has new config - do_fb(); - - framesize = 6; // vga - repeat = 100; // 100 videos - xspeed = 1; // playback at 1 x realtime - gray = 0; // not gray - - quality = 10; // quality 10 - pretty good. Goes from 0..63, but 0-5 sometimes fails on bright scenery (jpg too big for ESP32CAM system) - capture_interval = 200; // 200 milli-secconds per frame - total_frames = 9000; // 9000 frames x 200 ms = 1800 sec = 30 min - - xlength = total_frames * capture_interval / 1000; - - newfile = 0; // no file is open // don't fiddle with this! - recording = 1; // start recording on reboot without sending a command - -} - -// -// if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS -// -void major_fail() { - while (1) { - digitalWrite(33, LOW); - delay(100); - digitalWrite(33, HIGH); - delay(100); - - digitalWrite(33, LOW); - delay(100); - digitalWrite(33, HIGH); - delay(100); - - digitalWrite(33, LOW); - delay(100); - digitalWrite(33, HIGH); - //delay(100); - - delay(1000); - - digitalWrite(33, LOW); - delay(500); - digitalWrite(33, HIGH); - delay(500); - - digitalWrite(33, LOW); - delay(500); - digitalWrite(33, HIGH); - delay(500); - - digitalWrite(33, LOW); - delay(500); - digitalWrite(33, HIGH); - //delay(500); - - delay(1000); - } -} - - -bool init_wifi() -{ - int connAttempts = 0; - - // zzz - // Set your Static IP address - IPAddress local_IP(192, 168, 1, 222); - - // Set your Gateway IP address - IPAddress gateway(192, 168, 1, 254); - - IPAddress subnet(255, 255, 0, 0); - IPAddress primaryDNS(8, 8, 8, 8); // optional - IPAddress secondaryDNS(8, 8, 4, 4); // optional - - WiFi.setHostname("ESP32CAM-222"); // does not seem to do anything with my wifi router ??? - - if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { - Serial.println("STA Failed to configure"); - major_fail(); - } - - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED ) { - delay(500); - Serial.print("."); - if (connAttempts > 10) return false; - connAttempts++; - } - return true; -} - -void init_time() -{ - - sntp_setoperatingmode(SNTP_OPMODE_POLL); - sntp_setservername(0, "pool.ntp.org"); - sntp_setservername(1, "time.windows.com"); - sntp_setservername(2, "time.nist.gov"); - - sntp_init(); - - // wait for time to be set - time_t now = 0; - timeinfo = { 0 }; - int retry = 0; - const int retry_count = 10; - 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(2000); - time(&now); - localtime_r(&now, &timeinfo); - } - - if (timeinfo.tm_year < (2016 - 1900)) { - major_fail(); - } -} - -static esp_err_t init_sdcard() -{ - esp_err_t ret = ESP_FAIL; - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = false, - .max_files = 10, - }; - sdmmc_card_t *card; - - Serial.println("Mounting SD 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)); - major_fail(); - } - - Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? -} - -////////////////////////////////////////////////////////////// -// save photo stuff not currently used - -static esp_err_t save_photo_numbered() -{ - file_number++; - Serial.print("Taking picture: "); - Serial.print(file_number); - camera_fb_t *fb = esp_camera_fb_get(); - - char *filename = (char*)malloc(21 + sizeof(int)); - sprintf(filename, "/sdcard/capture_%d.jpg", file_number); - - Serial.println(filename); - FILE *file = fopen(filename, "w"); - if (file != NULL) { - size_t err = fwrite(fb->buf, 1, fb->len, file); - Serial.printf("File saved: %s\n", filename); - } else { - Serial.println("Could not open file"); - } - fclose(file); - esp_camera_fb_return(fb); - free(filename); -} - - - -static esp_err_t save_photo_dated() -{ - Serial.println("Taking a picture..."); - camera_fb_t *fb = esp_camera_fb_get(); - - time(&now); - localtime_r(&now, &timeinfo); - strftime(strftime_buf, sizeof(strftime_buf), "%F__%H%M%S", &timeinfo); - - char fname[100]; - - if (framesize == 6) { - sprintf(fname, "/sdcard/%s_vga_%d.jpg", strftime_buf, quality); - } else if (framesize == 7) { - sprintf(fname, "/sdcard/%s_svga_%d.jpg", strftime_buf, quality); - } else if (framesize == 10) { - sprintf(fname, "/sdcard/%s_uxga_%d.jpg", strftime_buf, quality); - } else if (framesize == 5) { - sprintf(fname, "/sdcard/%s_cif_%d.jpg", strftime_buf, quality); - } else { - Serial.println("Wrong framesize"); - sprintf(fname, "/sdcard/%s_xxx_%d.jpg", strftime_buf, quality); - } - - FILE *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); - esp_camera_fb_return(fb); - -} - -void save_photo() -{ - if (timeinfo.tm_year < (2016 - 1900) || internet_connected == false) { // if no internet or time not set - save_photo_numbered(); // filenames in numbered order - } else { - save_photo_dated(); // filenames with date and time - } -} - - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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 - -// 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; - -camera_fb_t * fb = NULL; - -FILE *avifile = NULL; -FILE *idxfile = NULL; - -void make_avi( ) { - - // we are recording, but no file is open - - if (newfile == 0 && recording == 1) { // open the file - - //save_photo_dated(); - - start_avi(); - - digitalWrite(33, HIGH); - newfile = 1; - totalp = 0; - totalw = 0; - frame_cnt = 0; - frames_so_far = 0; - - } 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 - - } else { - - if (newfile == 1 && recording == 1) { // regular recording - - if (frames_so_far == total_frames) { // we are done the recording - - Serial.println("Done capture for total frames!"); - - 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; - } else { - recording = 0; - } - - } else if ((millis() - startms) > (total_frames * capture_interval)) { - - 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; - } else { - recording = 0; - } - - } else { // regular - - current_millis = millis(); - - if (current_millis - last_capture_millis > capture_interval) { // Take another picture - fixed interval - - //if ((current_millis - startms) > (frame_cnt * capture_interval)) { // Take another picture - with catch up - - last_capture_millis = millis(); - - frames_so_far = frames_so_far + 1; - frame_cnt++; - - another_pic_avi(); - } - } - } - } - } -} - -static esp_err_t start_avi() { - - //Serial.println("Starting an avi "); - - do_fb(); // start the camera ... warm it up - delay(1000); - do_fb(); - delay(1000); - do_fb(); - - time(&now); - localtime_r(&now, &timeinfo); - - strftime(strftime_buf, sizeof(strftime_buf), "%F__%H-%M-%S", &timeinfo); - - char fname[100]; - - if (framesize == 6) { - sprintf(fname, "/sdcard/%s_vga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 7) { - sprintf(fname, "/sdcard/%s_svga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 10) { - sprintf(fname, "/sdcard/%s_uxga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 5) { - sprintf(fname, "/sdcard/%s_cif_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else { - Serial.println("Wrong framesize"); - sprintf(fname, "/sdcard/%s_xxx_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } - - 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(); - } - - // -- open large file and initialize -- doesn't help much - - //fseek(avifile, 1 * 1024 * 1024, SEEK_SET); // start with 1 MB file - //size_t ze_err = fwrite(zero_buf, 1, 4, avifile); - //fseek(avifile, 0, SEEK_SET); - - - 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); - - if (framesize == 6) { - - 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 == 10) { - - 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 == 7) { - - 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 == 5) { - - 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; - -} // end of start avi - - -static esp_err_t another_pic_avi() { - - digitalWrite(33, LOW); - - bp = millis(); - fb = esp_camera_fb_get(); - totalp = totalp + millis() - bp; - - jpeg_size = fb->len; - movi_size += jpeg_size; // Update totals - uVideoLen += jpeg_size; - - size_t dc_err = fwrite(dc_buf, 1, 4, avifile); - size_t ze_err = fwrite(zero_buf, 1, 4, avifile); - - bw = millis(); - size_t err = fwrite(fb->buf, 1, fb->len, avifile); - - totalw = totalw + millis() - bw; - - digitalWrite(33, HIGH); // red led is on durng the photo and the write - - if (millis() - bigdelta > capture_interval * 1.25 ) { // how many are 25 % overtime - overtime_count = overtime_count + 1; - //Serial.print(millis()-bigdelta);Serial.print(","); - } - - if (millis() - bigdelta > capture_interval * 2 ) { // if we are 100% overtime - - //if (frame_cnt % 10 == 1) { - - Serial.print("Frame: "); Serial.print(frame_cnt); - Serial.print(" size "); Serial.print(err); - Serial.print(" delta "); Serial.print(millis() - bigdelta); - Serial.print(" > "); Serial.print(capture_interval); - - Serial.print(" avg pic "); Serial.print( totalp / frame_cnt ); - Serial.print(" avg wrt "); Serial.print( totalw / frame_cnt ); - - Serial.print(" overtime "); Serial.print( overtime_count ); Serial.print(" "); Serial.print( 100.0 * overtime_count / frame_cnt, 1 ); Serial.println(" %"); - - } - bigdelta = millis(); - - 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); - } - esp_camera_fb_return(fb); - - 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); - - 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); - - -} // end of another_pic_avi - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -static esp_err_t end_avi() { - - unsigned long current_end = 0; - - 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, 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( totalp / frame_cnt ); - Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt ); - Serial.print("Frames 25%+ late % "); Serial.println( 100.0 * overtime_count / frame_cnt, 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); - - Serial.println("---"); - WiFi.printDiag(Serial); - Serial.println("---"); - - //do_time(); -} - -//~~~~~~~~~~~~~~~~~~ -static esp_err_t do_fb() { - - camera_fb_t * fb = esp_camera_fb_get(); - - Serial.print("Pic, len="); Serial.println(fb->len); - - esp_camera_fb_return(fb); -} - -void do_time() { - //Serial.println(" ... wake up wifi stack ... "); - - //WiFi.reconnect(); - - /* - int numberOfNetworks = WiFi.scanNetworks(); - - Serial.print("Number of networks found: "); - Serial.println(numberOfNetworks); - - - HTTPClient http_wake; - - http_wake.begin("http://postman-echo.com/time/now"); - //http_wake.begin("http://192.168.1.254"); - - //http_wake.begin("http://google.com"); - - int httpCode = http_wake.GET(); - - if (httpCode > 0) { //Check for the returning code - - String payload = http_wake.getString(); - Serial.println(httpCode); - Serial.println(payload); - } - - else { - Serial.println("Error on HTTP request"); - } - - http_wake.end(); //Free the resources) - */ - -} - -//////////////////////////////////////////////////////////////////////////////////// -// -// some globals for the loop() -// - -long wakeup; -long last_wakeup = 0; - - -void loop() -{ - - if (WiFi.status() != WL_CONNECTED) { - init_wifi(); - Serial.println("***** WiFi reconnect *****"); - } - - wakeup = millis(); - if (wakeup - last_wakeup > (10 * 60 * 1000) ) { // 10 minutes - last_wakeup = millis(); - - //init_wifi(); - //Serial.println("... wakeup call ..."); - //do_time(); - } - - - ftpSrv.handleFTP(); - - make_avi(); - -} - - -//////////////////////////////////////////////////////////////////////////////////// -static esp_err_t capture_handler(httpd_req_t *req) { - - camera_fb_t * fb = NULL; - esp_err_t res = ESP_OK; - char fname[100]; - - fb = esp_camera_fb_get(); - if (!fb) { - Serial.println("Camera capture failed"); - httpd_resp_send_500(req); - 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); - - 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("Stopping previous recording"); - - 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[80]; - 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_length = capture_interval * total_frames; - - int new_framesize = s->status.framesize; - int new_quality = s->status.quality; - int new_repeat = 0; - int new_xspeed = 1; - int new_xlength = 3; - int new_gray = 0; - - 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 > 1 && x <= 3600 ) { - new_length = 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); - } - if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { - if (strcmp(new_res, "UXGA") == 0) { - new_framesize = 10; - } else if (strcmp(new_res, "SVGA") == 0) { - new_framesize = 7; - } else if (strcmp(new_res, "VGA") == 0) { - new_framesize = 6; - } else if (strcmp(new_res, "CIF") == 0) { - new_framesize = 5; - } else { - Serial.println("Only UXGA, 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 > 1 && x <= 50) { - new_quality = x; - } - - 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 <= 100) { - 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 == 1 ) { - new_gray = x; - } - - ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); - } - - if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { - - int x = atoi(param); - if (x >= 1 && x <= 100000) { - new_interval = x; - } - ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); - } - } - } - - s->set_quality(s, new_quality); - s->set_framesize(s, (framesize_t)new_framesize); - if (new_gray == 1) { - s->set_special_effect(s, 2); // 0 regular, 2 grayscale - } else { - s->set_special_effect(s, 0); // 0 regular, 2 grayscale - } - - framesize = new_framesize; - capture_interval = new_interval; - xlength = new_length; - total_frames = new_length * 1000 / capture_interval; - repeat = new_repeat; - quality = new_quality; - xspeed = new_xspeed; - gray = new_gray; - - do_fb(); - //delay(1000); - do_fb(); // let camera warm up - - do_start("Starting a new AVI"); - httpd_resp_send(req, the_page, strlen(the_page)); - return ESP_OK; - - } -} - -//////////////////////////////////////////////////////////////////////////////////// -void do_start(char *the_message) { - - Serial.print("do_start "); Serial.println(the_message); - - const char msg[] PROGMEM = R"rawliteral( - - - - -ESP32-CAM Video Recorder - - -

ESP32-CAM Video Recorder v23


-

Message is %s


- Recording = %d (1 is active)
- Capture Interval = %d ms
- Length = %d seconds
- Quality = %d (5 best to 63 worst)
- Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Speed = %d
- Gray = %d

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

ESP32-CAM Video Recorder v23


-

Message is %s


-

http://%s/


- Information and viewfinder

-

http://%s/start?framesize=VGA&length=1800&interval=200&quality=10&repeat=100&speed=1&gray=0


- Gives you VGA 640x480, video of 1800 seconds (30 min), picture every 200 ms, jpeg quality 10, repeat for 100 more of the same and play back at 1x actual fps, and don't make it grayscale

-

UXGA 2 sec per frame, for 30 minutes repeat, 30x playback


-

UXGA 5 fps for 30 minutes repeat


-

CIF 20 fps second for 30 minutes repeat


- -
-
- - -)rawliteral"; - - - sprintf(the_page, msg, the_message, localip, localip, localip, localip, localip, localip, localip); - - //Serial.println(strlen(msg)); - //Serial.println(msg); - //Serial.println(strlen(the_page)); - //Serial.println(the_page); - -} - - -//////////////////////////////////////////////////////////////////////////////////// -void do_status(char *the_message) { - - Serial.print("do_status "); Serial.println(the_message); - - const char msg[] PROGMEM = R"rawliteral( - - - - -ESP32-CAM Video Recorder - - -

ESP32-CAM Video Recorder v23


-

Message is %s


- Recording = %d (1 is active)
- Frame %d of %d
- Capture Interval = %d ms
- Length = %d seconds
- Quality = %d (5 best to 63 worst)
- Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Playback Speed = %d
- Gray = %d

- Commands as follows for your ESP's ip address:

-

http://%s/


- Information and viewfinder

-

http://%s/stop


- You must "stop" before starting with new parameters

-
-

ftp://%s/


- Username: esp, Password: esp ... to download the files

- Red LED on back of ESP will flash with every frame, and flash SOS if camera or sd card not working.
- -
-Check camera position with the frames below every 5 seconds for 5 pictures
-Refresh page for more.
-
-
- -)rawliteral"; - - - //Serial.print("Serving web page, len= "); Serial.println(strlen(msg)); - //Serial.println(msg); - - sprintf(the_page, msg, the_message, recording, frames_so_far, total_frames, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip); - - //Serial.println(strlen(the_page)); - //Serial.println(the_page); - -} - - -//////////////////////////////////////////////////////////////////////////////////// -static esp_err_t index_handler(httpd_req_t *req) { - - do_status("Refresh Status"); - - httpd_resp_send(req, the_page, strlen(the_page)); - return ESP_OK; -} - -void startCameraServer() { - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - - 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 - }; - - 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); - } -} +/* + + 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 + 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 + + Acknowlegements: + + 1. https://robotzero.one/time-lapse-esp32-cameras/ + Timelapse programs for ESP32-CAM version that sends snapshots of screen. + 2. https://github.com/nailbuster/esp8266FTPServer + ftp server (slightly modifed to get the directory function working) + 3. https://github.com/ArduCAM/Arduino/tree/master/ArduCAM/examples/mini + ArduCAM Mini demo (C)2017 LeeWeb: http://www.ArduCAM.com + I copied the structure of the avi file, some calculations. + + The is Arduino code, with standard setup for ESP32-CAM + - Board ESP32 Wrover Module + - Partition Scheme Huge APP (3MB No OTA) + + This program records an AVI video on the SD Card of an ESP32-CAM. + + It will record realtime video at limited framerates, or timelapses with the full resolution of the ESP32-CAM. + It is controlled by a web page it serves to stop and start recordings with many parameters, and look through the viewfinder. + + You can control framesize (UXGA, VGA, ...), quality, length, and fps to record, and fps to playback later, etc. + + There is also an ftp server to download the recordings to a PC. + + Instructions: + + The program uses a fixed IP of 192.168.1.222, so you can browse to it from your phone or computer. + + http://192.168.1.222/ -- this gives you the status of the recording in progress and lets you look through the viewfinder + + http://192.168.1.222/stop -- this stops the recording in progress and displays some sample commands to start new recordings + + ftp://192.168.1.222/ -- gives you the ftp server + + The ftp for esp32 seems to not be a full ftp. The Chrome Browser and the Windows command line ftp's did not work with this, but + the Raspbarian command line ftp works fine, and an old Windows ftp I have called CoffeeCup Free FTP also works, which is what I have been using. + You can download at about 450 KB/s -- which is better than having to retreive the SD chip if you camera is up in a tree! + + http://192.168.1.222/start?framesize=VGA&length=1800&interval=250&quality=10&repeat=100&speed=1&gray=0 -- this is a sample to start a new recording + + framesize can be UXGA, SVGA, VGA, CIF (default VGA) + length is length in seconds of the recording 0..3600 (default 1800) + interval is the milli-seconds between frames (default 200) + quality is a number 5..50 for the jpeg - smaller number is higher quality with bigger and more detailed jpeg (default 10) + repeat is a number of who many of the same recordings should be made (default 100) + speed is a factor to speed up realtime for a timelapse recording - 1 is realtime (default 1) + gray is 1 for a grayscale video (default 0 - color) + + These factors have to be within the limit of the SD chip to receive the data. + For example, using a LEXAR 300x 32GB microSDHC UHS-I, the following works for me: + + UXGA quality 10, 2 fps (or interval of 500ms) + SVGA quality 10, 5 fps (200ms) + VGA quality 10, 10 fps (100ms) + CIG quality 10, 20 fps (50ms) + + If you increase fps, you might have to reduce quality or framesize to keep it from dropping frames as it writes all the data to the SD chip. + + Also, other SD chips will be faster or slower. I was using a SanDisk 16GB microSDHC "Up to 653X" - which was slower and more unpredictable than the LEXAR ??? + + Search for "zzz" to find places to modify the code for: + 1. Your wifi name and password + 2. Your preferred ip address (with default gateway, etc) + 3. Your Timezone for use in filenames + 4. Defaults for framesize, quality, ... and if the recording should start on reboot of the ESP32 without receiving a command + +*/ + + + +//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#include "esp_log.h" +#include "esp_http_server.h" +#include "esp_camera.h" + +//#include // redundant + +#include "ESP32FtpServer.h" +#include + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + + +// Time +#include "time.h" +#include "lwip/err.h" +#include "lwip/apps/sntp.h" + +// MicroSD +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "esp_vfs_fat.h" + +long current_millis; +long last_capture_millis = 0; +static esp_err_t cam_err; +static esp_err_t card_err; +char strftime_buf[64]; +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; + +// 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 + +// +// +// EDIT ssid and password +// +// zzz +const char* ssid = "ssid123"; +const char* password = "ssidpassword"; + +// these are just declarations -- look below to edit defaults + +int capture_interval = 200; // microseconds between captures +int total_frames = 300; // default updated below +int recording = 1; // default start recording on reboot +int framesize = 6; // vga +int repeat = 100; // capture 100 videos +int quality = 10; +int xspeed = 1; +int xlength = 3; +int gray = 0; + + +#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}; // +uint8_t svga_h[2] = {0x58, 0x02}; // +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, 0x67, + 0x6D, 0x61, 0x69, 0x6C, 0x4C, 0x49, 0x53, 0x54, 0x00, 0x01, 0x0E, 0x00, 0x6D, 0x6F, 0x76, 0x69, +}; + + +// +// Writes an uint32_t in Big Endian at current file position +// +static void inline print_quartet(unsigned long i, FILE * fd) +{ + uint8_t x[1]; + + 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[3000]; + +char localip[20]; +WiFiEventId_t eventID; + +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" + + +void setup() { + //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems + + Serial.begin(115200); + + Serial.setDebugOutput(true); + + // zzz + Serial.println(" "); + Serial.println("-------------------------------------"); + Serial.println("ESP-CAM Video Recorder v23"); + Serial.println(" ip 192.168.1.222 "); + Serial.println("-------------------------------------"); + + pinMode(33, OUTPUT); // little red led on back of chip + + digitalWrite(33, LOW); // turn on the red LED on the back of chip + + eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.print("WiFi lost connection. Reason: "); + Serial.println(info.disconnected.reason); + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("*** connected/disconnected issue! WiFi disconnected ???..."); + WiFi.disconnect(); + } else { + Serial.println("*** WiFi disconnected ???..."); + } + }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); + + + if (init_wifi()) { // Connected to WiFi + internet_connected = true; + Serial.println("Internet connected"); + init_time(); + time(&now); + //setenv("TZ", "GMT0BST,M3.5.0/01,M10.5.0/02", 1); + // zzz + setenv("TZ", "MST7MDT,M3.2.0/2:00:00,M11.1.0/2:00:00", 1); // mountain time zone + tzset(); + } + + camera_config_t config; + config.ledc_channel = LEDC_CHANNEL_0; + config.ledc_timer = LEDC_TIMER_0; + config.pin_d0 = Y2_GPIO_NUM; + config.pin_d1 = Y3_GPIO_NUM; + config.pin_d2 = Y4_GPIO_NUM; + config.pin_d3 = Y5_GPIO_NUM; + config.pin_d4 = Y6_GPIO_NUM; + config.pin_d5 = Y7_GPIO_NUM; + config.pin_d6 = Y8_GPIO_NUM; + config.pin_d7 = Y9_GPIO_NUM; + config.pin_xclk = XCLK_GPIO_NUM; + config.pin_pclk = PCLK_GPIO_NUM; + config.pin_vsync = VSYNC_GPIO_NUM; + config.pin_href = HREF_GPIO_NUM; + config.pin_sscb_sda = SIOD_GPIO_NUM; + config.pin_sscb_scl = SIOC_GPIO_NUM; + config.pin_pwdn = PWDN_GPIO_NUM; + config.pin_reset = RESET_GPIO_NUM; + config.xclk_freq_hz = 20000000; + config.pixel_format = PIXFORMAT_JPEG; + + //init with high specs to pre-allocate larger buffers + if (psramFound()) { + config.frame_size = FRAMESIZE_UXGA; + config.jpeg_quality = 1; + config.fb_count = 2; + } else { + config.frame_size = FRAMESIZE_SVGA; // svga 12 fails due to jpg 60000 + config.jpeg_quality = 12; + config.fb_count = 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(); + return; + } + + // 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; + } + + // 200 ms x 150 frames = 30 seconds is 3 MB indoors + // 200 ms x 300 frames = 1 minute is about 6MD indoor + // 20 ms x 3000 frames = 10 minute is 60 MB indoor + // burst 1000 frames gives 8 fps rather than 5, so 2 minutues 20 MB indoor + + startCameraServer(); + + Serial.print("Camera Ready! Use 'http://"); + Serial.print(WiFi.localIP()); + Serial.println("' to connect"); + + sprintf(localip, "%s", WiFi.localIP().toString().c_str()); + Serial.print("localip "); Serial.println(localip); + + // zzz username and password for ftp server + + ftpSrv.begin("esp", "esp"); + digitalWrite(33, HIGH); + + + // + // startup defaults -- EDIT HERE + // zzz + + sensor_t * s = esp_camera_sensor_get(); + s->set_framesize(s, FRAMESIZE_VGA); + s->set_quality(s, 10); + do_fb(); // do a couple captures to make sure camera has new config + do_fb(); + + framesize = 6; // vga + repeat = 100; // 100 videos + xspeed = 1; // playback at 1 x realtime + gray = 0; // not gray + + quality = 10; // quality 10 - pretty good. Goes from 0..63, but 0-5 sometimes fails on bright scenery (jpg too big for ESP32CAM system) + capture_interval = 200; // 200 milli-secconds per frame + total_frames = 9000; // 9000 frames x 200 ms = 1800 sec = 30 min + + xlength = total_frames * capture_interval / 1000; + + newfile = 0; // no file is open // don't fiddle with this! + recording = 1; // start recording on reboot without sending a command + +} + +// +// if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS +// +void major_fail() { + while (1) { + digitalWrite(33, LOW); + delay(100); + digitalWrite(33, HIGH); + delay(100); + + digitalWrite(33, LOW); + delay(100); + digitalWrite(33, HIGH); + delay(100); + + digitalWrite(33, LOW); + delay(100); + digitalWrite(33, HIGH); + //delay(100); + + delay(1000); + + digitalWrite(33, LOW); + delay(500); + digitalWrite(33, HIGH); + delay(500); + + digitalWrite(33, LOW); + delay(500); + digitalWrite(33, HIGH); + delay(500); + + digitalWrite(33, LOW); + delay(500); + digitalWrite(33, HIGH); + //delay(500); + + delay(1000); + } +} + + +bool init_wifi() +{ + int connAttempts = 0; + + // zzz + // Set your Static IP address + IPAddress local_IP(192, 168, 1, 222); + + // Set your Gateway IP address + IPAddress gateway(192, 168, 1, 254); + + IPAddress subnet(255, 255, 0, 0); + IPAddress primaryDNS(8, 8, 8, 8); // optional + IPAddress secondaryDNS(8, 8, 4, 4); // optional + + WiFi.setHostname("ESP32CAM-222"); // does not seem to do anything with my wifi router ??? + + if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { + Serial.println("STA Failed to configure"); + major_fail(); + } + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED ) { + delay(500); + Serial.print("."); + if (connAttempts > 10) return false; + connAttempts++; + } + return true; +} + +void init_time() +{ + + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, "pool.ntp.org"); + sntp_setservername(1, "time.windows.com"); + sntp_setservername(2, "time.nist.gov"); + + sntp_init(); + + // wait for time to be set + time_t now = 0; + timeinfo = { 0 }; + int retry = 0; + const int retry_count = 10; + 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(2000); + time(&now); + localtime_r(&now, &timeinfo); + } + + if (timeinfo.tm_year < (2016 - 1900)) { + major_fail(); + } +} + +static esp_err_t init_sdcard() +{ + esp_err_t ret = ESP_FAIL; + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 10, + }; + sdmmc_card_t *card; + + Serial.println("Mounting SD 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)); + major_fail(); + } + + Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? +} + +////////////////////////////////////////////////////////////// +// save photo stuff not currently used + +static esp_err_t save_photo_numbered() +{ + file_number++; + Serial.print("Taking picture: "); + Serial.print(file_number); + camera_fb_t *fb = esp_camera_fb_get(); + + char *filename = (char*)malloc(21 + sizeof(int)); + sprintf(filename, "/sdcard/capture_%d.jpg", file_number); + + Serial.println(filename); + FILE *file = fopen(filename, "w"); + if (file != NULL) { + size_t err = fwrite(fb->buf, 1, fb->len, file); + Serial.printf("File saved: %s\n", filename); + } else { + Serial.println("Could not open file"); + } + fclose(file); + esp_camera_fb_return(fb); + free(filename); +} + + + +static esp_err_t save_photo_dated() +{ + Serial.println("Taking a picture..."); + camera_fb_t *fb = esp_camera_fb_get(); + + time(&now); + localtime_r(&now, &timeinfo); + strftime(strftime_buf, sizeof(strftime_buf), "%F__%H%M%S", &timeinfo); + + char fname[100]; + + if (framesize == 6) { + sprintf(fname, "/sdcard/%s_vga_%d.jpg", strftime_buf, quality); + } else if (framesize == 7) { + sprintf(fname, "/sdcard/%s_svga_%d.jpg", strftime_buf, quality); + } else if (framesize == 10) { + sprintf(fname, "/sdcard/%s_uxga_%d.jpg", strftime_buf, quality); + } else if (framesize == 5) { + sprintf(fname, "/sdcard/%s_cif_%d.jpg", strftime_buf, quality); + } else { + Serial.println("Wrong framesize"); + sprintf(fname, "/sdcard/%s_xxx_%d.jpg", strftime_buf, quality); + } + + FILE *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); + esp_camera_fb_return(fb); + +} + +void save_photo() +{ + if (timeinfo.tm_year < (2016 - 1900) || internet_connected == false) { // if no internet or time not set + save_photo_numbered(); // filenames in numbered order + } else { + save_photo_dated(); // filenames with date and time + } +} + + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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 + +// 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; + +camera_fb_t * fb = NULL; + +FILE *avifile = NULL; +FILE *idxfile = NULL; + +void make_avi( ) { + + // we are recording, but no file is open + + if (newfile == 0 && recording == 1) { // open the file + + //save_photo_dated(); + + start_avi(); + + digitalWrite(33, HIGH); + newfile = 1; + totalp = 0; + totalw = 0; + frame_cnt = 0; + frames_so_far = 0; + + } 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 + + } else { + + if (newfile == 1 && recording == 1) { // regular recording + + if (frames_so_far == total_frames) { // we are done the recording + + Serial.println("Done capture for total frames!"); + + 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; + } else { + recording = 0; + } + + } else if ((millis() - startms) > (total_frames * capture_interval)) { + + 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; + } else { + recording = 0; + } + + } else { // regular + + current_millis = millis(); + + if (current_millis - last_capture_millis > capture_interval) { // Take another picture - fixed interval + + //if ((current_millis - startms) > (frame_cnt * capture_interval)) { // Take another picture - with catch up + + last_capture_millis = millis(); + + frames_so_far = frames_so_far + 1; + frame_cnt++; + + another_pic_avi(); + } + } + } + } + } +} + +static esp_err_t start_avi() { + + //Serial.println("Starting an avi "); + + do_fb(); // start the camera ... warm it up + delay(1000); + do_fb(); + delay(1000); + do_fb(); + + time(&now); + localtime_r(&now, &timeinfo); + + strftime(strftime_buf, sizeof(strftime_buf), "%F__%H-%M-%S", &timeinfo); + + char fname[100]; + + if (framesize == 6) { + sprintf(fname, "/sdcard/%s_vga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 7) { + sprintf(fname, "/sdcard/%s_svga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 10) { + sprintf(fname, "/sdcard/%s_uxga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 5) { + sprintf(fname, "/sdcard/%s_cif_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else { + Serial.println("Wrong framesize"); + sprintf(fname, "/sdcard/%s_xxx_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } + + 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(); + } + + // -- open large file and initialize -- doesn't help much + + //fseek(avifile, 1 * 1024 * 1024, SEEK_SET); // start with 1 MB file + //size_t ze_err = fwrite(zero_buf, 1, 4, avifile); + //fseek(avifile, 0, SEEK_SET); + + + 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); + + if (framesize == 6) { + + 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 == 10) { + + 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 == 7) { + + 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 == 5) { + + 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; + +} // end of start avi + + +static esp_err_t another_pic_avi() { + + digitalWrite(33, LOW); + + bp = millis(); + fb = esp_camera_fb_get(); + totalp = totalp + millis() - bp; + + jpeg_size = fb->len; + movi_size += jpeg_size; // Update totals + uVideoLen += jpeg_size; + + size_t dc_err = fwrite(dc_buf, 1, 4, avifile); + size_t ze_err = fwrite(zero_buf, 1, 4, avifile); + + bw = millis(); + size_t err = fwrite(fb->buf, 1, fb->len, avifile); + + totalw = totalw + millis() - bw; + + digitalWrite(33, HIGH); // red led is on durng the photo and the write + + if (millis() - bigdelta > capture_interval * 1.25 ) { // how many are 25 % overtime + overtime_count = overtime_count + 1; + //Serial.print(millis()-bigdelta);Serial.print(","); + } + + if (millis() - bigdelta > capture_interval * 2 ) { // if we are 100% overtime + + //if (frame_cnt % 10 == 1) { + + Serial.print("Frame: "); Serial.print(frame_cnt); + Serial.print(" size "); Serial.print(err); + Serial.print(" delta "); Serial.print(millis() - bigdelta); + Serial.print(" > "); Serial.print(capture_interval); + + Serial.print(" avg pic "); Serial.print( totalp / frame_cnt ); + Serial.print(" avg wrt "); Serial.print( totalw / frame_cnt ); + + Serial.print(" overtime "); Serial.print( overtime_count ); Serial.print(" "); Serial.print( 100.0 * overtime_count / frame_cnt, 1 ); Serial.println(" %"); + + } + bigdelta = millis(); + + 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); + } + esp_camera_fb_return(fb); + + 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); + + 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); + + +} // end of another_pic_avi + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static esp_err_t end_avi() { + + unsigned long current_end = 0; + + 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, 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( totalp / frame_cnt ); + Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt ); + Serial.print("Frames 25%+ late % "); Serial.println( 100.0 * overtime_count / frame_cnt, 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); + + Serial.println("---"); + WiFi.printDiag(Serial); + Serial.println("---"); + + //do_time(); +} + +//~~~~~~~~~~~~~~~~~~ +static esp_err_t do_fb() { + + camera_fb_t * fb = esp_camera_fb_get(); + + Serial.print("Pic, len="); Serial.println(fb->len); + + esp_camera_fb_return(fb); +} + +void do_time() { + //Serial.println(" ... wake up wifi stack ... "); + + //WiFi.reconnect(); + + /* + int numberOfNetworks = WiFi.scanNetworks(); + + Serial.print("Number of networks found: "); + Serial.println(numberOfNetworks); + + + HTTPClient http_wake; + + http_wake.begin("http://postman-echo.com/time/now"); + //http_wake.begin("http://192.168.1.254"); + + //http_wake.begin("http://google.com"); + + int httpCode = http_wake.GET(); + + if (httpCode > 0) { //Check for the returning code + + String payload = http_wake.getString(); + Serial.println(httpCode); + Serial.println(payload); + } + + else { + Serial.println("Error on HTTP request"); + } + + http_wake.end(); //Free the resources) + */ + +} + +//////////////////////////////////////////////////////////////////////////////////// +// +// some globals for the loop() +// + +long wakeup; +long last_wakeup = 0; + + +void loop() +{ + + if (WiFi.status() != WL_CONNECTED) { + init_wifi(); + Serial.println("***** WiFi reconnect *****"); + } + + wakeup = millis(); + if (wakeup - last_wakeup > (10 * 60 * 1000) ) { // 10 minutes + last_wakeup = millis(); + + //init_wifi(); + //Serial.println("... wakeup call ..."); + //do_time(); + } + + + ftpSrv.handleFTP(); + + make_avi(); + +} + + +//////////////////////////////////////////////////////////////////////////////////// +static esp_err_t capture_handler(httpd_req_t *req) { + + camera_fb_t * fb = NULL; + esp_err_t res = ESP_OK; + char fname[100]; + + fb = esp_camera_fb_get(); + if (!fb) { + Serial.println("Camera capture failed"); + httpd_resp_send_500(req); + 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); + + 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("Stopping previous recording"); + + 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[80]; + 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_length = capture_interval * total_frames; + + int new_framesize = s->status.framesize; + int new_quality = s->status.quality; + int new_repeat = 0; + int new_xspeed = 1; + int new_xlength = 3; + int new_gray = 0; + + 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 > 1 && x <= 3600 ) { + new_length = 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); + } + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 10; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 7; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 6; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 5; + } else { + Serial.println("Only UXGA, 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 > 1 && x <= 50) { + new_quality = x; + } + + 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 <= 100) { + 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 == 1 ) { + new_gray = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); + } + + if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 1 && x <= 100000) { + new_interval = x; + } + ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); + } + } + } + + s->set_quality(s, new_quality); + s->set_framesize(s, (framesize_t)new_framesize); + if (new_gray == 1) { + s->set_special_effect(s, 2); // 0 regular, 2 grayscale + } else { + s->set_special_effect(s, 0); // 0 regular, 2 grayscale + } + + framesize = new_framesize; + capture_interval = new_interval; + xlength = new_length; + total_frames = new_length * 1000 / capture_interval; + repeat = new_repeat; + quality = new_quality; + xspeed = new_xspeed; + gray = new_gray; + + do_fb(); + //delay(1000); + do_fb(); // let camera warm up + + do_start("Starting a new AVI"); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; + + } +} + +//////////////////////////////////////////////////////////////////////////////////// +void do_start(char *the_message) { + + Serial.print("do_start "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +ESP32-CAM Video Recorder + + +

ESP32-CAM Video Recorder v23


+

Message is %s


+ Recording = %d (1 is active)
+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (5 best to 63 worst)
+ Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d

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

ESP32-CAM Video Recorder v23


+

Message is %s


+

http://%s/


+ Information and viewfinder

+

http://%s/start?framesize=VGA&length=1800&interval=200&quality=10&repeat=100&speed=1&gray=0


+ Gives you VGA 640x480, video of 1800 seconds (30 min), picture every 200 ms, jpeg quality 10, repeat for 100 more of the same and play back at 1x actual fps, and don't make it grayscale

+

UXGA 2 sec per frame, for 30 minutes repeat, 30x playback


+

UXGA 5 fps for 30 minutes repeat


+

CIF 20 fps second for 30 minutes repeat


+ +
+
+ + +)rawliteral"; + + + sprintf(the_page, msg, the_message, localip, localip, localip, localip, localip, localip, localip); + + //Serial.println(strlen(msg)); + //Serial.println(msg); + //Serial.println(strlen(the_page)); + //Serial.println(the_page); + +} + + +//////////////////////////////////////////////////////////////////////////////////// +void do_status(char *the_message) { + + Serial.print("do_status "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +ESP32-CAM Video Recorder + + +

ESP32-CAM Video Recorder v23


+

Message is %s


+ Recording = %d (1 is active)
+ Frame %d of %d
+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (5 best to 63 worst)
+ Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Playback Speed = %d
+ Gray = %d

+ Commands as follows for your ESP's ip address:

+

http://%s/


+ Information and viewfinder

+

http://%s/stop


+ You must "stop" before starting with new parameters

+
+

ftp://%s/


+ Username: esp, Password: esp ... to download the files

+ Red LED on back of ESP will flash with every frame, and flash SOS if camera or sd card not working.
+ +
+Check camera position with the frames below every 5 seconds for 5 pictures
+Refresh page for more.
+
+
+ +)rawliteral"; + + + //Serial.print("Serving web page, len= "); Serial.println(strlen(msg)); + //Serial.println(msg); + + sprintf(the_page, msg, the_message, recording, frames_so_far, total_frames, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip); + + //Serial.println(strlen(the_page)); + //Serial.println(the_page); + +} + + +//////////////////////////////////////////////////////////////////////////////////// +static esp_err_t index_handler(httpd_req_t *req) { + + do_status("Refresh Status"); + + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +void startCameraServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + 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 + }; + + 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); + } +} diff --git a/v39/ESP32FtpServer.cpp b/old/v39/ESP32FtpServer.cpp similarity index 96% rename from v39/ESP32FtpServer.cpp rename to old/v39/ESP32FtpServer.cpp index c0a0eb5..04022cb 100644 --- a/v39/ESP32FtpServer.cpp +++ b/old/v39/ESP32FtpServer.cpp @@ -1,1138 +1,1138 @@ -/* - * 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" - - - -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; - } -} - -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()); - if(file.isDirectory()){ - data.println( "01-01-2000 00:00AM " + fn); - //Serial.println( "01-01-2000 00:00AM " + fn); - } else { - 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; -} +/* + * 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" + + + +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; + } +} + +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()); + if(file.isDirectory()){ + data.println( "01-01-2000 00:00AM " + fn); + //Serial.println( "01-01-2000 00:00AM " + fn); + } else { + 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/v39/ESP32FtpServer.h b/old/v39/ESP32FtpServer.h similarity index 97% rename from v39/ESP32FtpServer.h rename to old/v39/ESP32FtpServer.h index 27f2c58..333b892 100644 --- a/v39/ESP32FtpServer.h +++ b/old/v39/ESP32FtpServer.h @@ -1,112 +1,112 @@ - -/* -* 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. - - -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 + +/* +* 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. + + +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/v39/README.md b/old/v39/README.md similarity index 100% rename from v39/README.md rename to old/v39/README.md diff --git a/v39/TimeLapseAvi39x.ino b/old/v39/TimeLapseAvi39x.ino similarity index 96% rename from v39/TimeLapseAvi39x.ino rename to old/v39/TimeLapseAvi39x.ino index 6e4d7b6..ae3536d 100644 --- a/v39/TimeLapseAvi39x.ino +++ b/old/v39/TimeLapseAvi39x.ino @@ -1,1683 +1,1683 @@ -#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 - - ~~~ - Update Sep 15, 2019 TimeLapseAvi39x.ino - - work-in-progress - - I'm publishing this as a few people have been asking or working on this - - - program now uses both cpu's with cpu 0 taking pictures and queue'ing them - and a separate task on cpu 1 moving the pictures from the queue and adding - them to the avi file on the sd card - - the loop() task on cpu 1 now just handles the ftp system and http server - - dropped fixed ip and switch to mDNS with name "desklens", which can be typed into - browser, and also used as wifi name on router - - small change to ftp to cooperate with WinSCP program - - fixed bug so Windows would calulcate the correct length (time length) of avi - - when queue of frames gets full, it slips every other frame to try to catch up - - camera is re-configued when changing from UXGA <> VGA to allow for more buffers - with the smaller frames - ~~~ - - The is Arduino code, with standard setup for ESP32-CAM - - Board ESP32 Wrover Module - - Partition Scheme Huge APP (3MB No OTA) - - This program records an AVI video on the SD Card of an ESP32-CAM. - - It will record realtime video at limited framerates, or timelapses with the full resolution of the ESP32-CAM. - It is controlled by a web page it serves to stop and start recordings with many parameters, and look through the viewfinder. - - You can control framesize (UXGA, VGA, ...), quality, length, and fps to record, and fps to playback later, etc. - - There is also an ftp server to download the recordings to a PC. - - Instructions: - - The program uses a fixed IP of 192.168.1.188, so you can browse to it from your phone or computer. - - http://192.168.1.188/ -- this gives you the status of the recording in progress and lets you look through the viewfinder - - http://192.168.1.188/stop -- this stops the recording in progress and displays some sample commands to start new recordings - - ftp://192.168.1.188/ -- gives you the ftp server - - The ftp for esp32 seems to not be a full ftp. The Chrome Browser and the Windows command line ftp's did not work with this, but - the Raspbarian command line ftp works fine, and an old Windows ftp I have called CoffeeCup Free FTP also works, which is what I have been using. - You can download at about 450 KB/s -- which is better than having to retreive the SD chip if you camera is up in a tree! - - http://192.168.1.188/start?framesize=VGA&length=1800&interval=250&quality=10&repeat=100&speed=1&gray=0 -- this is a sample to start a new recording - - framesize can be UXGA, SVGA, VGA, CIF (default VGA) - length is length in seconds of the recording 0..3600 (default 1800) - interval is the milli-seconds between frames (default 200) - quality is a number 5..50 for the jpeg - smaller number is higher quality with bigger and more detailed jpeg (default 10) - repeat is a number of who many of the same recordings should be made (default 100) - speed is a factor to speed up realtime for a timelapse recording - 1 is realtime (default 1) - gray is 1 for a grayscale video (default 0 - color) - - These factors have to be within the limit of the SD chip to receive the data. - For example, using a LEXAR 300x 32GB microSDHC UHS-I, the following works for me: - - UXGA quality 10, 2 fps (or interval of 500ms) - SVGA quality 10, 5 fps (200ms) - VGA quality 10, 10 fps (100ms) - CIG quality 10, 20 fps (50ms) - - If you increase fps, you might have to reduce quality or framesize to keep it from dropping frames as it writes all the data to the SD chip. - - Also, other SD chips will be faster or slower. I was using a SanDisk 16GB microSDHC "Up to 653X" - which was slower and more unpredictable than the LEXAR ??? - - Search for "zzz" to find places to modify the code for: - 1. Your wifi name and password - 2. Your preferred ip address (with default gateway, etc) - 3. Your Timezone for use in filenames - 4. Defaults for framesize, quality, ... and if the recording should start on reboot of the ESP32 without receiving a command - - Acknowlegements: - - 1. https://robotzero.one/time-lapse-esp32-cameras/ - Timelapse programs for ESP32-CAM version that sends snapshots of screen. - 2. https://github.com/nailbuster/esp8266FTPServer - ftp server (slightly modifed to get the directory function working) - 3. https://github.com/ArduCAM/Arduino/tree/master/ArduCAM/examples/mini - ArduCAM Mini demo (C)2017 LeeWeb: http://www.ArduCAM.com - I copied the structure of the avi file, some calculations. - -*/ - - -//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE -#include "esp_log.h" -#include "esp_http_server.h" -#include "esp_camera.h" - -//#include // redundant -#include - -#include "ESP32FtpServer.h" -#include - -FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial - - -// Time -#include "time.h" -#include "lwip/err.h" -#include "lwip/apps/sntp.h" - -// MicroSD -#include "driver/sdmmc_host.h" -#include "driver/sdmmc_defs.h" -#include "sdmmc_cmd.h" -#include "esp_vfs_fat.h" - -long current_millis; -long last_capture_millis = 0; -static esp_err_t cam_err; -static esp_err_t card_err; -char strftime_buf[64]; -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; - -// 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 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; - -// -// -// EDIT ssid and password -// -// zzz -const char* ssid = "Cable314"; -const char* password = "ABC1234ABC"; - - -// these are just declarations -- look below to edit defaults - -int capture_interval = 200; // microseconds between captures -int total_frames = 300; // default updated below -int recording = 0; // turned off until start of setup -int framesize = 6; // vga -int repeat = 100; // capture 100 videos -int quality = 10; -int xspeed = 1; -int xlength = 3; -int gray = 0; -int new_config = 0; - -#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}; // -uint8_t svga_h[2] = {0x58, 0x02}; // -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, 0x33, 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; -SemaphoreHandle_t baton; -int counter = 0; - -void codeForAviWriterTask( void * parameter ) -{ - - print_stats("AviWriterTask runs on Core: "); - - for (;;) { - make_avi(); - delay(1); - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// CameraTask runs on cpu 0 to take pictures and drop them in a queue -// - -void codeForCameraTask( void * parameter ) -{ - - print_stats("CameraTask runs on Core: "); - - for (;;) { - - if (other_cpu_active == 1 ) { - current_millis = millis(); - if (current_millis - last_capture_millis > capture_interval) { - - last_capture_millis = millis(); - - xSemaphoreTake( baton, portMAX_DELAY ); - - if ( ( (fb_in + fb_max - fb_out) % fb_max) + 1 == fb_max ) { - xSemaphoreGive( baton ); - - Serial.print(" S "); // the queue is full - skipped++; - skipping = 1; - - //Serial.print(" Q: "); Serial.println( (fb_in + fb_max - fb_out) % fb_max ); - //Serial.print(fb_in); Serial.print(" / "); Serial.print(fb_out); Serial.print(" / "); Serial.println(fb_max); - //delay(1); - - } if (skipping > 0 ) { - - if (skipping % 2 == 0) { // skip every other frame until queue is cleared - - frames_so_far = frames_so_far + 1; - frame_cnt++; - - fb_in = (fb_in + 1) % fb_max; - bp = millis(); - fb_q[fb_in] = esp_camera_fb_get(); - totalp = totalp - bp + millis(); - - } else { - //Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print("-s "); // skip an extra frame to empty the queue - skipped++; - } - skipping = skipping + 1; - if (((fb_in + fb_max - fb_out) % fb_max) == 0 ) { - skipping = 0; - Serial.print(" == "); - } - - xSemaphoreGive( baton ); - - } else { - - skipping = 0; - frames_so_far = frames_so_far + 1; - frame_cnt++; - - fb_in = (fb_in + 1) % fb_max; - bp = millis(); - fb_q[fb_in] = esp_camera_fb_get(); - totalp = totalp - bp + millis(); - xSemaphoreGive( baton ); - - } - - - } else { - //delay(5); // waiting to take next picture - } - } else { - //delay(50); // big delay if not recording - } - 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 x[1]; - - 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[3000]; - -char localip[20]; -WiFiEventId_t eventID; - -#include "soc/soc.h" -#include "soc/rtc_cntl_reg.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// setup() runs on cpu 1 -// - -void setup() { - //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems - - Serial.begin(115200); - - Serial.setDebugOutput(true); - - // zzz - Serial.println(" "); - Serial.println("-------------------------------------"); - Serial.println("ESP-CAM Video Recorder v39\n"); - Serial.println(" http://desklens.local - to access the camera\n"); - Serial.println("-------------------------------------"); - - print_stats("Begin setup Core: "); - - pinMode(33, OUTPUT); // little red led on back of chip - - digitalWrite(33, LOW); // turn on the red LED on the back of chip - - - eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { - Serial.print("WiFi lost connection. Reason: "); - Serial.println(info.disconnected.reason); - - if (WiFi.status() == WL_CONNECTED) { - Serial.println("*** connected/disconnected issue! WiFi disconnected ???..."); - WiFi.disconnect(); - } else { - Serial.println("*** WiFi disconnected ???..."); - } - }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); - - - if (init_wifi()) { // Connected to WiFi - internet_connected = true; - Serial.println("Internet connected"); - sprintf(localip, "%s", WiFi.localIP().toString().c_str()); - - if (!MDNS.begin("desklens")) { - Serial.println("Error setting up MDNS responder!"); - } else { - Serial.println("mDNS responder started"); - } - - init_time(); - time(&now); - - //setenv("TZ", "GMT0BST,M3.5.0/01,M10.5.0/02", 1); - - // zzz - setenv("TZ", "MST7MDT,M3.2.0/2:00:00,M11.1.0/2:00:00", 1); // mountain time zone - tzset(); - delay(1000); - time(&now); - Serial.print("After timezone : "); Serial.println(ctime(&now)); - } - - baton = xSemaphoreCreateMutex(); - - xTaskCreatePinnedToCore( - codeForCameraTask, - "CameraTask", - 10000, - NULL, - 1, - &CameraTask, - 0); - - delay(500); - - xTaskCreatePinnedToCore( - codeForAviWriterTask, - "AviWriterTask", - 10000, - NULL, - 2, - &AviWriterTask, - 1); - - delay(500); - - print_stats("After Task 1 Core: "); - - if (psramFound()) { - } else { - Serial.println("paraFound wrong - major fail"); - major_fail(); - } - - // 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; - } - - print_stats("After SD init Core: "); - - startCameraServer(); - - print_stats("After Server init Core: "); - - // zzz username and password for ftp server - - ftpSrv.begin("esp", "esp"); - - print_stats("After ftp init Core: "); - - digitalWrite(33, HIGH); - - // - // startup defaults -- EDIT HERE - // zzz - - framesize = 10; // uxga - repeat = 100; // 100 files - xspeed = 30; // 30x playback speed - gray = 0; // not gray - quality = 10; // 10 on the 0..64 scale, or 10..50 subscale - capture_interval = 1000; // 1000 ms or 1 second - total_frames = 1800; // 1800 frames or 60 x 30 = 30 minutes - xlength = total_frames * capture_interval / 1000; - - new_config = 5; // 5 means we have not configured the camera - // 1 setup as vga, 2 setup as uxga - // 3 move from uxga -> vga - // 4 move from vga -> uxga - - newfile = 0; // no file is open // don't fiddle with this! - recording = 0; // we are NOT recording - - config_camera(); - - recording = 1; // we are recording - - - Serial.print("Camera Ready! Use 'http://"); - Serial.print(WiFi.localIP()); - Serial.println("' to connect"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// print_stats to keep track of memory during debugging -// - -void print_stats(char *the_message) { - - Serial.print(the_message); Serial.println(xPortGetCoreID()); - Serial.print(" Free Heap: "); Serial.print(ESP.getFreeHeap()); - Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); - - printf(" Himem is %dKiB, Himem free %dKiB, ", (int)ESP.getPsramSize() / 1024, (int)ESP.getFreePsram() / 1024); - printf("Flash is %dKiB, Sketch is %dKiB \n", (int)ESP.getFlashChipSize() / 1024, (int)ESP.getSketchSize() / 1024); - - 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); - Serial.println(" "); -} - -// -// if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS -// -void major_fail() { - - for (int i = 0; i < 10; i++) { - digitalWrite(33, LOW); delay(150); - digitalWrite(33, HIGH); delay(150); - digitalWrite(33, LOW); delay(150); - digitalWrite(33, HIGH); delay(150); - digitalWrite(33, LOW); delay(150); - digitalWrite(33, HIGH); delay(150); - - delay(1000); - - digitalWrite(33, LOW); delay(500); - digitalWrite(33, HIGH); delay(500); - digitalWrite(33, LOW); delay(500); - digitalWrite(33, HIGH); delay(500); - digitalWrite(33, LOW); delay(500); - digitalWrite(33, HIGH); delay(500); - - delay(1000); - } - - ESP.restart(); - -} - - -bool init_wifi() -{ - int connAttempts = 0; - WiFi.mode(WIFI_STA); - - WiFi.setHostname("desklens"); - WiFi.printDiag(Serial); - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED ) { - delay(500); - Serial.print("."); - if (connAttempts > 10) { - Serial.println("Cannot connect - try again"); - WiFi.begin(ssid, password); - WiFi.printDiag(Serial); - } - if (connAttempts > 20) { - Serial.println("Cannot connect - fail"); - major_fail(); - return false; - WiFi.printDiag(Serial); - major_fail(); - return false; - } - - connAttempts++; - } - - WiFi.printDiag(Serial); - return true; - - /* - // this is the fixed ip stuff that does not work with with router - // zzz - // Set your Static IP address - IPAddress local_IP(192, 168, 1, 225); - //IPAddress local_IP(192, 169, 1, 225); - - // Set your Gateway IP address - IPAddress gateway(192, 168, 1, 254); - //IPAddress gateway(192, 169, 1, 1); - - IPAddress subnet(255, 255, 0, 0); - IPAddress primaryDNS(8, 8, 8, 8); // optional - IPAddress secondaryDNS(8, 8, 4, 4); // optional - - WiFi.mode(WIFI_STA); - - WiFi.setHostname("ESP32CAM225"); // does not seem to do anything with my wifi router ??? - - if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { - Serial.println("STA Failed to configure"); - major_fail(); - } - - WiFi.printDiag(Serial); - - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED ) { - delay(500); - Serial.print("."); - if (connAttempts > 10) { - Serial.println("Cannot connect"); - WiFi.printDiag(Serial); - major_fail(); - return false; - } - connAttempts++; - } - return true; - - */ -} - -void init_time() -{ - - do_time(); - - sntp_setoperatingmode(SNTP_OPMODE_POLL); - sntp_setservername(0, "pool.ntp.org"); - sntp_setservername(1, "time.windows.com"); - sntp_setservername(2, "time.nist.gov"); - - sntp_init(); - - // wait for time to be set - time_t now = 0; - timeinfo = { 0 }; - int retry = 0; - const int retry_count = 10; - 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(2000); - time(&now); - localtime_r(&now, &timeinfo); - Serial.println(ctime(&now)); - } - - if (timeinfo.tm_year < (2016 - 1900)) { - major_fail(); - } -} - -static esp_err_t init_sdcard() -{ - esp_err_t ret = ESP_FAIL; - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = false, - .max_files = 10, - }; - sdmmc_card_t *card; - - Serial.println("Mounting SD 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)); - major_fail(); - } - - Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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( ) { - - // we are recording, but no file is open - - if (newfile == 0 && recording == 1) { // open the file - - digitalWrite(33, HIGH); - newfile = 1; - start_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 - - } else { - - if (newfile == 1 && recording == 1) { // regular recording - - if (frames_so_far >= total_frames) { // we are done the recording - - Serial.println("Done capture for total frames!"); - - 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; - } else { - recording = 0; - } - - } else if ((millis() - startms) > (total_frames * capture_interval)) { - - 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; - } else { - recording = 0; - } - - } else { // regular - - another_save_avi(); - - } - } - } - } -} - -static esp_err_t config_camera() { - - camera_config_t config; - - Serial.println("config camera"); - - if (new_config > 2) { - - 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; - - if (new_config == 3) { - - config.frame_size = FRAMESIZE_VGA; - fb_max = 20; // from 12 - new_config = 1; - } else { - config.frame_size = FRAMESIZE_UXGA; - fb_max = 4; - new_config = 2; - } - - config.jpeg_quality = 5; - config.fb_count = fb_max; - - print_stats("Before deinit() runs on Core: "); - - esp_camera_deinit(); - - print_stats("After deinit() runs on Core: "); - - // 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(); - } - - print_stats("After the new init runs on Core: "); - - delay(500); - } - - 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 - } - - //Serial.println("after the sensor stuff"); - - for (int j = 0; j < 5; j++) { - do_fb(); // start the camera ... warm it up - delay(20); - } - -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// start_avi - open the files and write in headers -// - -static esp_err_t start_avi() { - - Serial.println("Starting an avi "); - - config_camera(); - - time(&now); - localtime_r(&now, &timeinfo); - - strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); - - char fname[100]; - - if (framesize == 6) { - sprintf(fname, "/sdcard/%s_vga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 7) { - sprintf(fname, "/sdcard/%s_svga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 10) { - sprintf(fname, "/sdcard/%s_uxga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 5) { - sprintf(fname, "/sdcard/%s_cif_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } else { - Serial.println("Wrong framesize"); - sprintf(fname, "/sdcard/%s_xxx_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); - } - - 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); - - if (framesize == 6) { - - 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 == 10) { - - 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 == 7) { - - 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 == 5) { - - 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; - - 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 ); - - } else { - - //if ( (fb_in + fb_max - fb_out) % fb_max > 3) { // more than 1 in queue ?? - //Serial.print(millis()); 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); - //} - - fb_out = (fb_out + 1) % fb_max; - - int fblen; - fblen = fb_q[fb_out]->len; - - //xSemaphoreGive( baton ); - - 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(); - size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile); - if (err == 0 ) { - Serial.println("Error on avi write"); - major_fail(); - } - - //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); - - 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); - //Serial.println("Write done"); - totalw = totalw + millis() - bw; - - //if (((fb_in + fb_max - fb_out) % fb_max) > 0 ) { - // Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print(" "); - //} - - 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 ; - - 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( 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, 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); - - Serial.println("---"); - //WiFi.printDiag(Serial); - -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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() { - - int numberOfNetworks = WiFi.scanNetworks(); - - Serial.print("Number of networks found: "); - Serial.println(numberOfNetworks); - -} - -//////////////////////////////////////////////////////////////////////////////////// -// -// some globals for the loop() -// - -long wakeup; -long last_wakeup = 0; - - -void loop() -{ - - if (WiFi.status() != WL_CONNECTED) { - init_wifi(); - Serial.println("***** WiFi reconnect *****"); - } - - wakeup = millis(); - if (wakeup - last_wakeup > (10 * 60 * 1000) ) { // 10 minutes - last_wakeup = millis(); - - print_stats("Wakeup in loop() Core: "); - } - - ftpSrv.handleFTP(); - delay(10); - -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// - -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 ); - 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("Stopping previous recording"); - - 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[80]; - 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_length = capture_interval * total_frames; - - int new_framesize = s->status.framesize; - int new_quality = s->status.quality; - int new_repeat = 0; - int new_xspeed = 1; - int new_xlength = 3; - int new_gray = 0; - - 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_length = 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); - } - if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { - if (strcmp(new_res, "UXGA") == 0) { - new_framesize = 10; - } else if (strcmp(new_res, "SVGA") == 0) { - new_framesize = 7; - } else if (strcmp(new_res, "VGA") == 0) { - new_framesize = 6; - } else if (strcmp(new_res, "CIF") == 0) { - new_framesize = 5; - } else { - Serial.println("Only UXGA, 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 >= 5 && x <= 50) { - new_quality = x; - } - - 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 <= 100) { - 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 == 1 ) { - new_gray = x; - } - - ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); - } - - if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { - - int x = atoi(param); - if (x >= 1 && x <= 108000) { // 108,000 ms = 30 min - new_interval = x; - } - ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); - } - } - } - - framesize = new_framesize; - capture_interval = new_interval; - xlength = new_length; - total_frames = new_length * 1000 / capture_interval; - repeat = new_repeat; - quality = new_quality; - xspeed = new_xspeed; - gray = new_gray; - - if ((new_config == 1) && (framesize > 6)) { - new_config = 4; - Serial.println("from VGA to UXGA"); - } else if ((new_config == 2) && (framesize < 7)) { - new_config = 3; - Serial.println("from UXGA to VGA"); - } - - - do_start("Starting a new AVI"); - httpd_resp_send(req, the_page, strlen(the_page)); - - - - recording = 1; - return ESP_OK; - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -void do_start(char *the_message) { - - Serial.print("do_start "); Serial.println(the_message); - - const char msg[] PROGMEM = R"rawliteral( - - - - -ESP32-CAM Video Recorder - - -

ESP32-CAM Video Recorder v39


-

Message is %s


- Recording = %d (1 is active)
- Capture Interval = %d ms
- Length = %d seconds
- Quality = %d (5 best to 50 worst)
- Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Speed = %d
- Gray = %d

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

ESP32-CAM Video Recorder v39


-

Message is %s


-

http://%s/


- Information and viewfinder

-

http://%s/start?framesize=VGA&length=1800&interval=83&quality=10&repeat=100&speed=1&gray=0


- VGA 12 fps - VGA 640x480, video of 1800 seconds (30 min), picture every 83 ms, jpeg quality 10, repeat for 100 more of the same and play back at 1x actual fps, and don't make it grayscale

-

UXGA 1 sec per frame, for 30 minutes repeat, 30x playback


-

UXGA 2 fps for 30 minutes repeat


-

CIF 24 fps second for 30 minutes repeat


- -
-
- - -)rawliteral"; - - - sprintf(the_page, msg, the_message, localip, localip, localip, localip, localip, localip, localip); - -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -void do_status(char *the_message) { - - Serial.print("do_status "); Serial.println(the_message); - - const char msg[] PROGMEM = R"rawliteral( - - - - -ESP32-CAM Video Recorder - - -

ESP32-CAM Video Recorder v39
%s


-

Message is %s


- Recording = %d (1 is active)
- Frame %d of %d, Skipped %d

- Capture Interval = %d ms
- Length = %d seconds
- Quality = %d (5 best to 50 worst)
- Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Playback Speed = %d
- Gray = %d

- Commands as follows for your ESP's ip address:

-

http://%s/


- Information and viewfinder

-

http://%s/stop ... and restart


- You must "stop" before starting with new parameters

-
-

ftp://%s/


- Username: esp, Password: esp ... to download the files

- Red LED on back of ESP will flash with every frame, and flash SOS if camera or sd card not working.
- -
-Check camera position with the frames below every 5 seconds for 5 pictures
-Refresh page for more.
-
-
- -)rawliteral"; - - time(&now); - const char *strdate = ctime(&now); - - - sprintf(the_page, msg, strdate, the_message, recording, frames_so_far, total_frames, skipped, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip); - -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -static esp_err_t index_handler(httpd_req_t *req) { - - print_stats("Index Handler Core: "); - - do_status("Refresh Status"); - httpd_resp_send(req, the_page, strlen(the_page)); - return ESP_OK; -} - -void startCameraServer() { - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - - 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 - }; - - 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); - } -} +#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 + + ~~~ + Update Sep 15, 2019 TimeLapseAvi39x.ino + - work-in-progress + - I'm publishing this as a few people have been asking or working on this + + - program now uses both cpu's with cpu 0 taking pictures and queue'ing them + and a separate task on cpu 1 moving the pictures from the queue and adding + them to the avi file on the sd card + - the loop() task on cpu 1 now just handles the ftp system and http server + - dropped fixed ip and switch to mDNS with name "desklens", which can be typed into + browser, and also used as wifi name on router + - small change to ftp to cooperate with WinSCP program + - fixed bug so Windows would calulcate the correct length (time length) of avi + - when queue of frames gets full, it slips every other frame to try to catch up + - camera is re-configued when changing from UXGA <> VGA to allow for more buffers + with the smaller frames + ~~~ + + The is Arduino code, with standard setup for ESP32-CAM + - Board ESP32 Wrover Module + - Partition Scheme Huge APP (3MB No OTA) + + This program records an AVI video on the SD Card of an ESP32-CAM. + + It will record realtime video at limited framerates, or timelapses with the full resolution of the ESP32-CAM. + It is controlled by a web page it serves to stop and start recordings with many parameters, and look through the viewfinder. + + You can control framesize (UXGA, VGA, ...), quality, length, and fps to record, and fps to playback later, etc. + + There is also an ftp server to download the recordings to a PC. + + Instructions: + + The program uses a fixed IP of 192.168.1.188, so you can browse to it from your phone or computer. + + http://192.168.1.188/ -- this gives you the status of the recording in progress and lets you look through the viewfinder + + http://192.168.1.188/stop -- this stops the recording in progress and displays some sample commands to start new recordings + + ftp://192.168.1.188/ -- gives you the ftp server + + The ftp for esp32 seems to not be a full ftp. The Chrome Browser and the Windows command line ftp's did not work with this, but + the Raspbarian command line ftp works fine, and an old Windows ftp I have called CoffeeCup Free FTP also works, which is what I have been using. + You can download at about 450 KB/s -- which is better than having to retreive the SD chip if you camera is up in a tree! + + http://192.168.1.188/start?framesize=VGA&length=1800&interval=250&quality=10&repeat=100&speed=1&gray=0 -- this is a sample to start a new recording + + framesize can be UXGA, SVGA, VGA, CIF (default VGA) + length is length in seconds of the recording 0..3600 (default 1800) + interval is the milli-seconds between frames (default 200) + quality is a number 5..50 for the jpeg - smaller number is higher quality with bigger and more detailed jpeg (default 10) + repeat is a number of who many of the same recordings should be made (default 100) + speed is a factor to speed up realtime for a timelapse recording - 1 is realtime (default 1) + gray is 1 for a grayscale video (default 0 - color) + + These factors have to be within the limit of the SD chip to receive the data. + For example, using a LEXAR 300x 32GB microSDHC UHS-I, the following works for me: + + UXGA quality 10, 2 fps (or interval of 500ms) + SVGA quality 10, 5 fps (200ms) + VGA quality 10, 10 fps (100ms) + CIG quality 10, 20 fps (50ms) + + If you increase fps, you might have to reduce quality or framesize to keep it from dropping frames as it writes all the data to the SD chip. + + Also, other SD chips will be faster or slower. I was using a SanDisk 16GB microSDHC "Up to 653X" - which was slower and more unpredictable than the LEXAR ??? + + Search for "zzz" to find places to modify the code for: + 1. Your wifi name and password + 2. Your preferred ip address (with default gateway, etc) + 3. Your Timezone for use in filenames + 4. Defaults for framesize, quality, ... and if the recording should start on reboot of the ESP32 without receiving a command + + Acknowlegements: + + 1. https://robotzero.one/time-lapse-esp32-cameras/ + Timelapse programs for ESP32-CAM version that sends snapshots of screen. + 2. https://github.com/nailbuster/esp8266FTPServer + ftp server (slightly modifed to get the directory function working) + 3. https://github.com/ArduCAM/Arduino/tree/master/ArduCAM/examples/mini + ArduCAM Mini demo (C)2017 LeeWeb: http://www.ArduCAM.com + I copied the structure of the avi file, some calculations. + +*/ + + +//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#include "esp_log.h" +#include "esp_http_server.h" +#include "esp_camera.h" + +//#include // redundant +#include + +#include "ESP32FtpServer.h" +#include + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + + +// Time +#include "time.h" +#include "lwip/err.h" +#include "lwip/apps/sntp.h" + +// MicroSD +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "esp_vfs_fat.h" + +long current_millis; +long last_capture_millis = 0; +static esp_err_t cam_err; +static esp_err_t card_err; +char strftime_buf[64]; +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; + +// 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 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; + +// +// +// EDIT ssid and password +// +// zzz +const char* ssid = "Cable314"; +const char* password = "ABC1234ABC"; + + +// these are just declarations -- look below to edit defaults + +int capture_interval = 200; // microseconds between captures +int total_frames = 300; // default updated below +int recording = 0; // turned off until start of setup +int framesize = 6; // vga +int repeat = 100; // capture 100 videos +int quality = 10; +int xspeed = 1; +int xlength = 3; +int gray = 0; +int new_config = 0; + +#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}; // +uint8_t svga_h[2] = {0x58, 0x02}; // +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, 0x33, 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; +SemaphoreHandle_t baton; +int counter = 0; + +void codeForAviWriterTask( void * parameter ) +{ + + print_stats("AviWriterTask runs on Core: "); + + for (;;) { + make_avi(); + delay(1); + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// CameraTask runs on cpu 0 to take pictures and drop them in a queue +// + +void codeForCameraTask( void * parameter ) +{ + + print_stats("CameraTask runs on Core: "); + + for (;;) { + + if (other_cpu_active == 1 ) { + current_millis = millis(); + if (current_millis - last_capture_millis > capture_interval) { + + last_capture_millis = millis(); + + xSemaphoreTake( baton, portMAX_DELAY ); + + if ( ( (fb_in + fb_max - fb_out) % fb_max) + 1 == fb_max ) { + xSemaphoreGive( baton ); + + Serial.print(" S "); // the queue is full + skipped++; + skipping = 1; + + //Serial.print(" Q: "); Serial.println( (fb_in + fb_max - fb_out) % fb_max ); + //Serial.print(fb_in); Serial.print(" / "); Serial.print(fb_out); Serial.print(" / "); Serial.println(fb_max); + //delay(1); + + } if (skipping > 0 ) { + + if (skipping % 2 == 0) { // skip every other frame until queue is cleared + + frames_so_far = frames_so_far + 1; + frame_cnt++; + + fb_in = (fb_in + 1) % fb_max; + bp = millis(); + fb_q[fb_in] = esp_camera_fb_get(); + totalp = totalp - bp + millis(); + + } else { + //Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print("-s "); // skip an extra frame to empty the queue + skipped++; + } + skipping = skipping + 1; + if (((fb_in + fb_max - fb_out) % fb_max) == 0 ) { + skipping = 0; + Serial.print(" == "); + } + + xSemaphoreGive( baton ); + + } else { + + skipping = 0; + frames_so_far = frames_so_far + 1; + frame_cnt++; + + fb_in = (fb_in + 1) % fb_max; + bp = millis(); + fb_q[fb_in] = esp_camera_fb_get(); + totalp = totalp - bp + millis(); + xSemaphoreGive( baton ); + + } + + + } else { + //delay(5); // waiting to take next picture + } + } else { + //delay(50); // big delay if not recording + } + 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 x[1]; + + 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[3000]; + +char localip[20]; +WiFiEventId_t eventID; + +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// setup() runs on cpu 1 +// + +void setup() { + //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems + + Serial.begin(115200); + + Serial.setDebugOutput(true); + + // zzz + Serial.println(" "); + Serial.println("-------------------------------------"); + Serial.println("ESP-CAM Video Recorder v39\n"); + Serial.println(" http://desklens.local - to access the camera\n"); + Serial.println("-------------------------------------"); + + print_stats("Begin setup Core: "); + + pinMode(33, OUTPUT); // little red led on back of chip + + digitalWrite(33, LOW); // turn on the red LED on the back of chip + + + eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.print("WiFi lost connection. Reason: "); + Serial.println(info.disconnected.reason); + + if (WiFi.status() == WL_CONNECTED) { + Serial.println("*** connected/disconnected issue! WiFi disconnected ???..."); + WiFi.disconnect(); + } else { + Serial.println("*** WiFi disconnected ???..."); + } + }, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); + + + if (init_wifi()) { // Connected to WiFi + internet_connected = true; + Serial.println("Internet connected"); + sprintf(localip, "%s", WiFi.localIP().toString().c_str()); + + if (!MDNS.begin("desklens")) { + Serial.println("Error setting up MDNS responder!"); + } else { + Serial.println("mDNS responder started"); + } + + init_time(); + time(&now); + + //setenv("TZ", "GMT0BST,M3.5.0/01,M10.5.0/02", 1); + + // zzz + setenv("TZ", "MST7MDT,M3.2.0/2:00:00,M11.1.0/2:00:00", 1); // mountain time zone + tzset(); + delay(1000); + time(&now); + Serial.print("After timezone : "); Serial.println(ctime(&now)); + } + + baton = xSemaphoreCreateMutex(); + + xTaskCreatePinnedToCore( + codeForCameraTask, + "CameraTask", + 10000, + NULL, + 1, + &CameraTask, + 0); + + delay(500); + + xTaskCreatePinnedToCore( + codeForAviWriterTask, + "AviWriterTask", + 10000, + NULL, + 2, + &AviWriterTask, + 1); + + delay(500); + + print_stats("After Task 1 Core: "); + + if (psramFound()) { + } else { + Serial.println("paraFound wrong - major fail"); + major_fail(); + } + + // 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; + } + + print_stats("After SD init Core: "); + + startCameraServer(); + + print_stats("After Server init Core: "); + + // zzz username and password for ftp server + + ftpSrv.begin("esp", "esp"); + + print_stats("After ftp init Core: "); + + digitalWrite(33, HIGH); + + // + // startup defaults -- EDIT HERE + // zzz + + framesize = 10; // uxga + repeat = 100; // 100 files + xspeed = 30; // 30x playback speed + gray = 0; // not gray + quality = 10; // 10 on the 0..64 scale, or 10..50 subscale + capture_interval = 1000; // 1000 ms or 1 second + total_frames = 1800; // 1800 frames or 60 x 30 = 30 minutes + xlength = total_frames * capture_interval / 1000; + + new_config = 5; // 5 means we have not configured the camera + // 1 setup as vga, 2 setup as uxga + // 3 move from uxga -> vga + // 4 move from vga -> uxga + + newfile = 0; // no file is open // don't fiddle with this! + recording = 0; // we are NOT recording + + config_camera(); + + recording = 1; // we are recording + + + Serial.print("Camera Ready! Use 'http://"); + Serial.print(WiFi.localIP()); + Serial.println("' to connect"); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// print_stats to keep track of memory during debugging +// + +void print_stats(char *the_message) { + + Serial.print(the_message); Serial.println(xPortGetCoreID()); + Serial.print(" Free Heap: "); Serial.print(ESP.getFreeHeap()); + Serial.print(", priority = "); Serial.println(uxTaskPriorityGet(NULL)); + + printf(" Himem is %dKiB, Himem free %dKiB, ", (int)ESP.getPsramSize() / 1024, (int)ESP.getFreePsram() / 1024); + printf("Flash is %dKiB, Sketch is %dKiB \n", (int)ESP.getFlashChipSize() / 1024, (int)ESP.getSketchSize() / 1024); + + 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); + Serial.println(" "); +} + +// +// if we have no camera, or sd card, then flash rear led on and off to warn the human SOS - SOS +// +void major_fail() { + + for (int i = 0; i < 10; i++) { + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + + delay(1000); + + digitalWrite(33, LOW); delay(500); + digitalWrite(33, HIGH); delay(500); + digitalWrite(33, LOW); delay(500); + digitalWrite(33, HIGH); delay(500); + digitalWrite(33, LOW); delay(500); + digitalWrite(33, HIGH); delay(500); + + delay(1000); + } + + ESP.restart(); + +} + + +bool init_wifi() +{ + int connAttempts = 0; + WiFi.mode(WIFI_STA); + + WiFi.setHostname("desklens"); + WiFi.printDiag(Serial); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED ) { + delay(500); + Serial.print("."); + if (connAttempts > 10) { + Serial.println("Cannot connect - try again"); + WiFi.begin(ssid, password); + WiFi.printDiag(Serial); + } + if (connAttempts > 20) { + Serial.println("Cannot connect - fail"); + major_fail(); + return false; + WiFi.printDiag(Serial); + major_fail(); + return false; + } + + connAttempts++; + } + + WiFi.printDiag(Serial); + return true; + + /* + // this is the fixed ip stuff that does not work with with router + // zzz + // Set your Static IP address + IPAddress local_IP(192, 168, 1, 225); + //IPAddress local_IP(192, 169, 1, 225); + + // Set your Gateway IP address + IPAddress gateway(192, 168, 1, 254); + //IPAddress gateway(192, 169, 1, 1); + + IPAddress subnet(255, 255, 0, 0); + IPAddress primaryDNS(8, 8, 8, 8); // optional + IPAddress secondaryDNS(8, 8, 4, 4); // optional + + WiFi.mode(WIFI_STA); + + WiFi.setHostname("ESP32CAM225"); // does not seem to do anything with my wifi router ??? + + if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { + Serial.println("STA Failed to configure"); + major_fail(); + } + + WiFi.printDiag(Serial); + + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED ) { + delay(500); + Serial.print("."); + if (connAttempts > 10) { + Serial.println("Cannot connect"); + WiFi.printDiag(Serial); + major_fail(); + return false; + } + connAttempts++; + } + return true; + + */ +} + +void init_time() +{ + + do_time(); + + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, "pool.ntp.org"); + sntp_setservername(1, "time.windows.com"); + sntp_setservername(2, "time.nist.gov"); + + sntp_init(); + + // wait for time to be set + time_t now = 0; + timeinfo = { 0 }; + int retry = 0; + const int retry_count = 10; + 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(2000); + time(&now); + localtime_r(&now, &timeinfo); + Serial.println(ctime(&now)); + } + + if (timeinfo.tm_year < (2016 - 1900)) { + major_fail(); + } +} + +static esp_err_t init_sdcard() +{ + esp_err_t ret = ESP_FAIL; + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 10, + }; + sdmmc_card_t *card; + + Serial.println("Mounting SD 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)); + major_fail(); + } + + Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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( ) { + + // we are recording, but no file is open + + if (newfile == 0 && recording == 1) { // open the file + + digitalWrite(33, HIGH); + newfile = 1; + start_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 + + } else { + + if (newfile == 1 && recording == 1) { // regular recording + + if (frames_so_far >= total_frames) { // we are done the recording + + Serial.println("Done capture for total frames!"); + + 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; + } else { + recording = 0; + } + + } else if ((millis() - startms) > (total_frames * capture_interval)) { + + 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; + } else { + recording = 0; + } + + } else { // regular + + another_save_avi(); + + } + } + } + } +} + +static esp_err_t config_camera() { + + camera_config_t config; + + Serial.println("config camera"); + + if (new_config > 2) { + + 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; + + if (new_config == 3) { + + config.frame_size = FRAMESIZE_VGA; + fb_max = 20; // from 12 + new_config = 1; + } else { + config.frame_size = FRAMESIZE_UXGA; + fb_max = 4; + new_config = 2; + } + + config.jpeg_quality = 5; + config.fb_count = fb_max; + + print_stats("Before deinit() runs on Core: "); + + esp_camera_deinit(); + + print_stats("After deinit() runs on Core: "); + + // 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(); + } + + print_stats("After the new init runs on Core: "); + + delay(500); + } + + 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 + } + + //Serial.println("after the sensor stuff"); + + for (int j = 0; j < 5; j++) { + do_fb(); // start the camera ... warm it up + delay(20); + } + +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// start_avi - open the files and write in headers +// + +static esp_err_t start_avi() { + + Serial.println("Starting an avi "); + + config_camera(); + + time(&now); + localtime_r(&now, &timeinfo); + + strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); + + char fname[100]; + + if (framesize == 6) { + sprintf(fname, "/sdcard/%s_vga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 7) { + sprintf(fname, "/sdcard/%s_svga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 10) { + sprintf(fname, "/sdcard/%s_uxga_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 5) { + sprintf(fname, "/sdcard/%s_cif_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } else { + Serial.println("Wrong framesize"); + sprintf(fname, "/sdcard/%s_xxx_Q%d_I%d_L%d_S%d.avi", strftime_buf, quality, capture_interval, xlength, xspeed); + } + + 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); + + if (framesize == 6) { + + 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 == 10) { + + 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 == 7) { + + 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 == 5) { + + 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; + + 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 ); + + } else { + + //if ( (fb_in + fb_max - fb_out) % fb_max > 3) { // more than 1 in queue ?? + //Serial.print(millis()); 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); + //} + + fb_out = (fb_out + 1) % fb_max; + + int fblen; + fblen = fb_q[fb_out]->len; + + //xSemaphoreGive( baton ); + + 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(); + size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile); + if (err == 0 ) { + Serial.println("Error on avi write"); + major_fail(); + } + + //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); + + 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); + //Serial.println("Write done"); + totalw = totalw + millis() - bw; + + //if (((fb_in + fb_max - fb_out) % fb_max) > 0 ) { + // Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print(" "); + //} + + 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 ; + + 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( 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, 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); + + Serial.println("---"); + //WiFi.printDiag(Serial); + +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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() { + + int numberOfNetworks = WiFi.scanNetworks(); + + Serial.print("Number of networks found: "); + Serial.println(numberOfNetworks); + +} + +//////////////////////////////////////////////////////////////////////////////////// +// +// some globals for the loop() +// + +long wakeup; +long last_wakeup = 0; + + +void loop() +{ + + if (WiFi.status() != WL_CONNECTED) { + init_wifi(); + Serial.println("***** WiFi reconnect *****"); + } + + wakeup = millis(); + if (wakeup - last_wakeup > (10 * 60 * 1000) ) { // 10 minutes + last_wakeup = millis(); + + print_stats("Wakeup in loop() Core: "); + } + + ftpSrv.handleFTP(); + delay(10); + +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// + +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 ); + 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("Stopping previous recording"); + + 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[80]; + 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_length = capture_interval * total_frames; + + int new_framesize = s->status.framesize; + int new_quality = s->status.quality; + int new_repeat = 0; + int new_xspeed = 1; + int new_xlength = 3; + int new_gray = 0; + + 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_length = 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); + } + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 10; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 7; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 6; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 5; + } else { + Serial.println("Only UXGA, 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 >= 5 && x <= 50) { + new_quality = x; + } + + 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 <= 100) { + 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 == 1 ) { + new_gray = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); + } + + if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 1 && x <= 108000) { // 108,000 ms = 30 min + new_interval = x; + } + ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); + } + } + } + + framesize = new_framesize; + capture_interval = new_interval; + xlength = new_length; + total_frames = new_length * 1000 / capture_interval; + repeat = new_repeat; + quality = new_quality; + xspeed = new_xspeed; + gray = new_gray; + + if ((new_config == 1) && (framesize > 6)) { + new_config = 4; + Serial.println("from VGA to UXGA"); + } else if ((new_config == 2) && (framesize < 7)) { + new_config = 3; + Serial.println("from UXGA to VGA"); + } + + + do_start("Starting a new AVI"); + httpd_resp_send(req, the_page, strlen(the_page)); + + + + recording = 1; + return ESP_OK; + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_start(char *the_message) { + + Serial.print("do_start "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +ESP32-CAM Video Recorder + + +

ESP32-CAM Video Recorder v39


+

Message is %s


+ Recording = %d (1 is active)
+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (5 best to 50 worst)
+ Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d

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

ESP32-CAM Video Recorder v39


+

Message is %s


+

http://%s/


+ Information and viewfinder

+

http://%s/start?framesize=VGA&length=1800&interval=83&quality=10&repeat=100&speed=1&gray=0


+ VGA 12 fps - VGA 640x480, video of 1800 seconds (30 min), picture every 83 ms, jpeg quality 10, repeat for 100 more of the same and play back at 1x actual fps, and don't make it grayscale

+

UXGA 1 sec per frame, for 30 minutes repeat, 30x playback


+

UXGA 2 fps for 30 minutes repeat


+

CIF 24 fps second for 30 minutes repeat


+ +
+
+ + +)rawliteral"; + + + sprintf(the_page, msg, the_message, localip, localip, localip, localip, localip, localip, localip); + +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_status(char *the_message) { + + Serial.print("do_status "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +ESP32-CAM Video Recorder + + +

ESP32-CAM Video Recorder v39
%s


+

Message is %s


+ Recording = %d (1 is active)
+ Frame %d of %d, Skipped %d

+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (5 best to 50 worst)
+ Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Playback Speed = %d
+ Gray = %d

+ Commands as follows for your ESP's ip address:

+

http://%s/


+ Information and viewfinder

+

http://%s/stop ... and restart


+ You must "stop" before starting with new parameters

+
+

ftp://%s/


+ Username: esp, Password: esp ... to download the files

+ Red LED on back of ESP will flash with every frame, and flash SOS if camera or sd card not working.
+ +
+Check camera position with the frames below every 5 seconds for 5 pictures
+Refresh page for more.
+
+
+ +)rawliteral"; + + time(&now); + const char *strdate = ctime(&now); + + + sprintf(the_page, msg, strdate, the_message, recording, frames_so_far, total_frames, skipped, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip); + +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t index_handler(httpd_req_t *req) { + + print_stats("Index Handler Core: "); + + do_status("Refresh Status"); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +void startCameraServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + 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 + }; + + 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); + } +} diff --git a/v60/ESP32FtpServer.cpp b/old/v60/ESP32FtpServer.cpp similarity index 96% rename from v60/ESP32FtpServer.cpp rename to old/v60/ESP32FtpServer.cpp index e720163..4769d07 100644 --- a/v60/ESP32FtpServer.cpp +++ b/old/v60/ESP32FtpServer.cpp @@ -1,1169 +1,1169 @@ -/* - * 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 - - -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; - } -} - -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 = 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; -} +/* + * 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 + + +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; + } +} + +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 = 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/v23/ESP32FtpServer.h b/old/v60/ESP32FtpServer.h similarity index 97% rename from v23/ESP32FtpServer.h rename to old/v60/ESP32FtpServer.h index a40186f..c9c57e5 100644 --- a/v23/ESP32FtpServer.h +++ b/old/v60/ESP32FtpServer.h @@ -1,112 +1,112 @@ - -/* -* 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. - - -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 + +/* +* 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. + + +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/v60/README.MD b/old/v60/README.MD similarity index 100% rename from v60/README.MD rename to old/v60/README.MD diff --git a/v60/TimeLapseAvi60x.ino b/old/v60/TimeLapseAvi60x.ino similarity index 96% rename from v60/TimeLapseAvi60x.ino rename to old/v60/TimeLapseAvi60x.ino index 8b8064e..75879b0 100644 --- a/v60/TimeLapseAvi60x.ino +++ b/old/v60/TimeLapseAvi60x.ino @@ -1,1542 +1,1542 @@ - - -#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) - -*/ - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// edit these parameters for your needs - -static const char vernum[] = "v60"; // version 60 Feb 26, 2020 - added dates/times to ftp - // version 59 Feb 23, 2020 - switch to 1 bit system, and pir -static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames - - -#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT - - -// 1 for blink red led with every sd card write, at your frame rate -// 0 for blink only for skipping frames and SOS if camera or sd is broken -#define BlinkWithWrite 0 - -// EDIT ssid and password -const char ssid[] = "wifiRouter"; // your wireless network name (SSID) -const char password[] = "wifipassword"; // your Wi-Fi network password - -// startup defaults for first recording - -// here are the recording options from the "retart web page" -// VGA 10 fps for 30 min, repeat, realtime http://192.168.0.117/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0 -// VGA 2 fps, for 30 minutes repeat, 30x playback http://192.168.0.117/start?framesize=VGA&length=1800&interval=500&quality=10&repeat=300&speed=30&gray=0 -// UXGA 1 sec per frame, for 30 minutes repeat, 30x playback http://192.168.0.117/start?framesize=UXGA&length=1800&interval=1000&quality=10&repeat=100&speed=30&gray=0 -// UXGA 2 fps for 30 minutes repeat, 15x playback http://192.168.0.117/start?framesize=UXGA&length=1800&interval=500&quality=10&repeat=100&speed=30&gray=0 -// CIF 20 fps second for 30 minutes repeat http://192.168.0.117/start?framesize=CIF&length=1800&interval=50&quality=10&repeat=100&speed=1&gray=0 - -// reboot startup parameters here - -int record_on_reboot = 1; // set to 1 to record, or 0 to NOT record on reboot -int framesize = 6; // vga (10 UXGA, 7 SVGA, 6 VGA, 5 CIF) -int repeat = 100; // 100 files -int xspeed = 1; // 1x playback speed (realtime is 1) -int gray = 0; // not gray -int quality = 10; // 10 on the 0..64 scale, or 10..50 subscale - 10 is good, 20 is grainy and smaller files -int capture_interval = 100; // 100 ms or 10 frames per second -int total_frames = 18000; // 18000 frames = 10 fps * 60 seconds * 30 minutes = half hour - -int PIRpin = 12; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -int new_config = 5; // this system abandoned ! -int xlength = total_frames * capture_interval / 1000; -int recording = 0; -int PIRstatus = 0; -int PIRrecording = 0; -int ready = 0; - -//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE -#include "esp_log.h" -#include "esp_http_server.h" -#include "esp_camera.h" - -#include - -#include "ESP32FtpServer.h" -#include - -FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial - -// 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]; -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; - -// 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 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 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, 0x36, 0x30, 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; -SemaphoreHandle_t baton; -int counter = 0; - -void codeForAviWriterTask( void * parameter ) -{ - - for (;;) { - if (ready) { - make_avi(); - } - delay(1); - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// CameraTask runs on cpu 0 to take pictures and drop them in a queue -// - -void codeForCameraTask( void * parameter ) -{ - - for (;;) { - - if (other_cpu_active == 1 ) { - current_millis = millis(); - if (current_millis - last_capture_millis > capture_interval) { - - last_capture_millis = millis(); - - xSemaphoreTake( baton, portMAX_DELAY ); - - if ( ( (fb_in + fb_max - fb_out) % fb_max) + 1 == fb_max ) { - xSemaphoreGive( baton ); - - Serial.print(" Queue Full, Skipping ... "); // the queue is full - skipped++; - skipping = 1; - - } - - if (skipping > 0 ) { - - - if (!BlinkWithWrite) { - digitalWrite(33, LOW); - } - - if (skipping % 2 == 0) { // skip every other frame until queue is cleared - - frames_so_far = frames_so_far + 1; - frame_cnt++; - - fb_in = (fb_in + 1) % fb_max; - bp = millis(); - fb_q[fb_in] = esp_camera_fb_get(); - totalp = totalp - bp + millis(); - - } else { - Serial.print(((fb_in + fb_max - fb_out) % fb_max)); // skip an extra frame to empty the queue - skipped++; - } - skipping = skipping + 1; - if (((fb_in + fb_max - fb_out) % fb_max) == 0 ) { - skipping = 0; - Serial.println(" Queue cleared. "); - } - - xSemaphoreGive( baton ); - - } else { - - skipping = 0; - frames_so_far = frames_so_far + 1; - frame_cnt++; - - fb_in = (fb_in + 1) % fb_max; - bp = millis(); - fb_q[fb_in] = esp_camera_fb_get(); - totalp = totalp - bp + millis(); - xSemaphoreGive( baton ); - - } - } - } - 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 x[1]; - - 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[3000]; - -char localip[20]; -WiFiEventId_t eventID; - -#include "soc/soc.h" -#include "soc/rtc_cntl_reg.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// setup() runs on cpu 1 -// - -void setup() { - //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems - - Serial.begin(115200); - - Serial.setDebugOutput(true); - - // 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("-------------------------------------"); - - pinMode(33, OUTPUT); // little red led on back of chip - digitalWrite(33, LOW); // turn on the red LED on the back of chip - - if (init_wifi()) { // Connected to WiFi - internet_connected = true; - } - - if (!psramFound()) { - Serial.println("paraFound wrong - major fail"); - major_fail(); - } - - // 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; - } - - startCameraServer(); - - // zzz username and password for ftp server - - ftpSrv.begin("esp", "esp"); - - Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); - Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); - - digitalWrite(33, HIGH); // red light turns off when setup is complete - - baton = xSemaphoreCreateMutex(); - - xTaskCreatePinnedToCore( - codeForCameraTask, - "CameraTask", - 10000, - NULL, - 1, - &CameraTask, - 0); - - delay(50); - - xTaskCreatePinnedToCore( - codeForAviWriterTask, - "AviWriterTask", - 10000, - NULL, - 2, - &AviWriterTask, - 1); - - delay(50); - - - recording = 0; // we are NOT recording - config_camera(); - - - pinMode(4, OUTPUT); // using 1 bit mode, shut off the Blinding Disk-Active Light - digitalWrite(4, LOW); - - pinMode(PIRpin, INPUT_PULLDOWN); // or PULLDOWN for active high - - newfile = 0; // no file is open // don't fiddle with this! - - recording = record_on_reboot; - - ready = 1; - - Serial.print("Camera Ready! Use 'http://"); - Serial.print(WiFi.localIP()); - Serial.println("' to connect"); - - -} - - -// -// 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 - digitalWrite(33, LOW); delay(150); - digitalWrite(33, HIGH); delay(150); - digitalWrite(33, LOW); delay(150); - digitalWrite(33, HIGH); delay(150); - digitalWrite(33, LOW); delay(150); - digitalWrite(33, HIGH); delay(150); - - delay(1000); - - digitalWrite(33, LOW); delay(500); - digitalWrite(33, HIGH); delay(500); - digitalWrite(33, LOW); delay(500); - digitalWrite(33, HIGH); delay(500); - 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; - - WiFi.disconnect(true); - WiFi.mode(WIFI_STA); - - WiFi.setHostname(devname); - //WiFi.printDiag(Serial); - WiFi.begin(ssid, password); - delay(1000); - while (WiFi.status() != WL_CONNECTED ) { - delay(500); - Serial.print("."); - if (connAttempts == 10) { - Serial.println("Cannot connect - try again"); - WiFi.begin(ssid, password); - WiFi.printDiag(Serial); - } - if (connAttempts == 20) { - Serial.println("Cannot connect - fail"); - - WiFi.printDiag(Serial); - return false; - } - connAttempts++; - } - - Serial.println("Internet connected"); - - //WiFi.printDiag(Serial); - - 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 = 10; - 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.println(ctime(&now)); - sprintf(localip, "%s", WiFi.localIP().toString().c_str()); - - return true; - -} - - -static esp_err_t init_sdcard() -{ - 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; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - slot_config.width = 1; // using 1 bit mode - //Serial.print("Slot config width should be 4 width: "); Serial.println(slot_config.width); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = false, - .max_files = 5, - }; - - //pinMode(4, OUTPUT); // using 1 bit mode, shut off the Blinding Disk-Active Light - //digitalWrite(4, LOW); - - sdmmc_card_t *card; - - Serial.println("Mounting SD 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)); - major_fail(); - } - sdmmc_card_print_info(stdout, card); - Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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( ) { - - - PIRstatus = digitalRead(PIRpin); - - - if (PIRstatus == 1) { - - 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.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; - } - } - } - - - // we are recording, but no file is open - - if (newfile == 0 && recording == 1) { // open the file - - digitalWrite(33, HIGH); - newfile = 1; - start_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 (frames_so_far >= total_frames) { // we are done the recording - - Serial.println("Done capture for total frames!"); - - 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; - } else { - recording = 0; - PIRrecording = 0; - } - - } else 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; - } 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; - - fb_max = 7; // for vga and uxga - config.jpeg_quality = 8; - 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 - } - - for (int j = 0; j < 3; j++) { - do_fb(); // start the camera ... warm it up - delay(1); - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// start_avi - open the files and write in headers -// - -static esp_err_t start_avi() { - - Serial.println("Starting an avi "); - - config_camera(); - - time(&now); - localtime_r(&now, &timeinfo); - - strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); - - char fname[100]; - - if (framesize == 6) { - sprintf(fname, "/sdcard/%s_%s_vga_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 7) { - sprintf(fname, "/sdcard/%s_%s_svga_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 10) { - sprintf(fname, "/sdcard/%s_%s_uxga_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); - } else if (framesize == 5) { - sprintf(fname, "/sdcard/%s_%s_cif_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); - } else { - Serial.println("Wrong framesize"); - sprintf(fname, "/sdcard/%s_%s_xxx_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); - } - - 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); - - if (framesize == 6) { - - 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 == 10) { - - 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 == 7) { - - 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 == 5) { - - 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; - - 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 ); - - } 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(); - size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile); - if (err == 0 ) { - Serial.println("Error on avi write"); - major_fail(); - } - 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); - - 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); - //Serial.println("Write done"); - //41 totalw = totalw + millis() - bw; - - //if (((fb_in + fb_max - fb_out) % fb_max) > 0 ) { - // Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print(" "); - //} - - 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( totalp / frame_cnt ); - Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt ); - Serial.print("Frames Skipped % "); Serial.println( 100.0 * skipped / 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(); - } - - sprintf(localip, "%s", WiFi.localIP().toString().c_str()); - } - -} - -//////////////////////////////////////////////////////////////////////////////////// -// -// some globals for the loop() -// - -long wakeup; -long last_wakeup = 0; - - -void loop() -{ - - wakeup = millis(); - if (wakeup - last_wakeup > (14 * 60 * 1000) ) { // 14 minutes - last_wakeup = millis(); - - do_time(); - } - - ftpSrv.handleFTP(); - - //delay(1); - -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// - -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 ); - 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("Stopping previous recording"); - - 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[80]; - 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_length = capture_interval * total_frames; - - int new_framesize = s->status.framesize; - int new_quality = s->status.quality; - int new_repeat = 0; - int new_xspeed = 1; - int new_xlength = 3; - int new_gray = 0; - - /* - 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_length = 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); - } - if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { - if (strcmp(new_res, "UXGA") == 0) { - new_framesize = 10; - } else if (strcmp(new_res, "SVGA") == 0) { - new_framesize = 7; - } else if (strcmp(new_res, "VGA") == 0) { - new_framesize = 6; - } else if (strcmp(new_res, "CIF") == 0) { - new_framesize = 5; - } else { - Serial.println("Only UXGA, 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 >= 10 && x <= 50) { // MINIMUM QUALITY 10 to save memory - new_quality = x; - } - - 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 <= 100) { - 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 == 1 ) { - new_gray = x; - } - - ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); - } - - if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { - - int x = atoi(param); - if (x >= 1 && x <= 180000) { // 180,000 ms = 3 min - new_interval = x; - } - ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); - } - } - } - - framesize = new_framesize; - capture_interval = new_interval; - xlength = new_length; - total_frames = new_length * 1000 / capture_interval; - repeat = new_repeat; - quality = new_quality; - xspeed = new_xspeed; - gray = new_gray; - - do_start("Starting a new AVI"); - httpd_resp_send(req, the_page, strlen(the_page)); - - recording = 1; - return ESP_OK; - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -void do_start(char *the_message) { - - Serial.print("do_start "); Serial.println(the_message); - - const char msg[] PROGMEM = R"rawliteral( - - - - -%s ESP32-CAM Video Recorder - - -

%s
ESP32-CAM Video Recorder %s


-

Message is %s


- Recording = %d (1 is active)
- Capture Interval = %d ms
- Length = %d seconds
- Quality = %d (10 best to 50 worst)
- Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Speed = %d
- Gray = %d

- -
-
- - -)rawliteral"; - - - sprintf(the_page, msg, devname, devname, vernum, the_message, recording, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray); - //Serial.println(strlen(msg)); - -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -void do_stop(char *the_message) { - - 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


-

Message is %s


-

http://%s/

-

http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0

-

VGA 2 fps, for 30 minutes repeat, 30x playback

-

UXGA 1 sec per frame, for 30 minutes repeat, 30x playback

-

UXGA 2 fps for 30 minutes repeat, 15x playback

-

CIF 20 fps second for 30 minutes repeat

-
- -)rawliteral"; - - sprintf(the_page, msg, devname, devname, vernum, the_message, localip, localip, localip, localip, localip, localip, localip, localip); - -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -void do_status(char *the_message) { - - Serial.print("do_status "); Serial.println(the_message); - - const char msg[] PROGMEM = R"rawliteral( - - - - -%s ESP32-CAM Video Recorder - - -

%s
ESP32-CAM Video Recorder %s
%s


-

Message is %s


- Total SD Space is %d MB, Used SD Space is %d MB
- Recording = %d (1 is active)
- Frame %d of %d, Skipped %d

- Capture Interval = %d ms
- Length = %d seconds
- Quality = %d (10 best to 50 worst)
- Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Playback Speed = %d
- Gray = %d

- Commands as follows for your ESP's ip address:
-

http://%s/

-

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

-

ftp://%s/

- Username: esp, Password: esp ... to download the files

- Red LED on back of ESP will flash with every frame (or skipped frames), and flash SOS if camera or sd card not working.
- -
-Check camera position with the frame below
-Refresh page for more.
-
-
- -)rawliteral"; - - time(&now); - const char *strdate = ctime(&now); - - //Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); - //Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); - - int tot = SD_MMC.totalBytes() / (1024 * 1024); - int use = SD_MMC.usedBytes() / (1024 * 1024); - - //Serial.print(strlen(msg)); Serial.print(" "); - - sprintf(the_page, msg, devname, devname, vernum, strdate, the_message, tot, use, recording, frames_so_far, total_frames, skipped, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip); - - //Serial.println(strlen(the_page)); -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// -static esp_err_t index_handler(httpd_req_t *req) { - - do_status("Refresh Status"); - httpd_resp_send(req, the_page, strlen(the_page)); - return ESP_OK; -} - -void startCameraServer() { - httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - - 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 - }; - - 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); - } - - Serial.println("Camera http started"); -} + + +#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) + +*/ + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// edit these parameters for your needs + +static const char vernum[] = "v60"; // version 60 Feb 26, 2020 - added dates/times to ftp + // version 59 Feb 23, 2020 - switch to 1 bit system, and pir +static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames + + +#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT + + +// 1 for blink red led with every sd card write, at your frame rate +// 0 for blink only for skipping frames and SOS if camera or sd is broken +#define BlinkWithWrite 0 + +// EDIT ssid and password +const char ssid[] = "wifiRouter"; // your wireless network name (SSID) +const char password[] = "wifipassword"; // your Wi-Fi network password + +// startup defaults for first recording + +// here are the recording options from the "retart web page" +// VGA 10 fps for 30 min, repeat, realtime http://192.168.0.117/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0 +// VGA 2 fps, for 30 minutes repeat, 30x playback http://192.168.0.117/start?framesize=VGA&length=1800&interval=500&quality=10&repeat=300&speed=30&gray=0 +// UXGA 1 sec per frame, for 30 minutes repeat, 30x playback http://192.168.0.117/start?framesize=UXGA&length=1800&interval=1000&quality=10&repeat=100&speed=30&gray=0 +// UXGA 2 fps for 30 minutes repeat, 15x playback http://192.168.0.117/start?framesize=UXGA&length=1800&interval=500&quality=10&repeat=100&speed=30&gray=0 +// CIF 20 fps second for 30 minutes repeat http://192.168.0.117/start?framesize=CIF&length=1800&interval=50&quality=10&repeat=100&speed=1&gray=0 + +// reboot startup parameters here + +int record_on_reboot = 1; // set to 1 to record, or 0 to NOT record on reboot +int framesize = 6; // vga (10 UXGA, 7 SVGA, 6 VGA, 5 CIF) +int repeat = 100; // 100 files +int xspeed = 1; // 1x playback speed (realtime is 1) +int gray = 0; // not gray +int quality = 10; // 10 on the 0..64 scale, or 10..50 subscale - 10 is good, 20 is grainy and smaller files +int capture_interval = 100; // 100 ms or 10 frames per second +int total_frames = 18000; // 18000 frames = 10 fps * 60 seconds * 30 minutes = half hour + +int PIRpin = 12; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +int new_config = 5; // this system abandoned ! +int xlength = total_frames * capture_interval / 1000; +int recording = 0; +int PIRstatus = 0; +int PIRrecording = 0; +int ready = 0; + +//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#include "esp_log.h" +#include "esp_http_server.h" +#include "esp_camera.h" + +#include + +#include "ESP32FtpServer.h" +#include + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + +// 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]; +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; + +// 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 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 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, 0x36, 0x30, 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; +SemaphoreHandle_t baton; +int counter = 0; + +void codeForAviWriterTask( void * parameter ) +{ + + for (;;) { + if (ready) { + make_avi(); + } + delay(1); + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// CameraTask runs on cpu 0 to take pictures and drop them in a queue +// + +void codeForCameraTask( void * parameter ) +{ + + for (;;) { + + if (other_cpu_active == 1 ) { + current_millis = millis(); + if (current_millis - last_capture_millis > capture_interval) { + + last_capture_millis = millis(); + + xSemaphoreTake( baton, portMAX_DELAY ); + + if ( ( (fb_in + fb_max - fb_out) % fb_max) + 1 == fb_max ) { + xSemaphoreGive( baton ); + + Serial.print(" Queue Full, Skipping ... "); // the queue is full + skipped++; + skipping = 1; + + } + + if (skipping > 0 ) { + + + if (!BlinkWithWrite) { + digitalWrite(33, LOW); + } + + if (skipping % 2 == 0) { // skip every other frame until queue is cleared + + frames_so_far = frames_so_far + 1; + frame_cnt++; + + fb_in = (fb_in + 1) % fb_max; + bp = millis(); + fb_q[fb_in] = esp_camera_fb_get(); + totalp = totalp - bp + millis(); + + } else { + Serial.print(((fb_in + fb_max - fb_out) % fb_max)); // skip an extra frame to empty the queue + skipped++; + } + skipping = skipping + 1; + if (((fb_in + fb_max - fb_out) % fb_max) == 0 ) { + skipping = 0; + Serial.println(" Queue cleared. "); + } + + xSemaphoreGive( baton ); + + } else { + + skipping = 0; + frames_so_far = frames_so_far + 1; + frame_cnt++; + + fb_in = (fb_in + 1) % fb_max; + bp = millis(); + fb_q[fb_in] = esp_camera_fb_get(); + totalp = totalp - bp + millis(); + xSemaphoreGive( baton ); + + } + } + } + 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 x[1]; + + 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[3000]; + +char localip[20]; +WiFiEventId_t eventID; + +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// setup() runs on cpu 1 +// + +void setup() { + //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector // creates other problems + + Serial.begin(115200); + + Serial.setDebugOutput(true); + + // 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("-------------------------------------"); + + pinMode(33, OUTPUT); // little red led on back of chip + digitalWrite(33, LOW); // turn on the red LED on the back of chip + + if (init_wifi()) { // Connected to WiFi + internet_connected = true; + } + + if (!psramFound()) { + Serial.println("paraFound wrong - major fail"); + major_fail(); + } + + // 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; + } + + startCameraServer(); + + // zzz username and password for ftp server + + ftpSrv.begin("esp", "esp"); + + Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); + + digitalWrite(33, HIGH); // red light turns off when setup is complete + + baton = xSemaphoreCreateMutex(); + + xTaskCreatePinnedToCore( + codeForCameraTask, + "CameraTask", + 10000, + NULL, + 1, + &CameraTask, + 0); + + delay(50); + + xTaskCreatePinnedToCore( + codeForAviWriterTask, + "AviWriterTask", + 10000, + NULL, + 2, + &AviWriterTask, + 1); + + delay(50); + + + recording = 0; // we are NOT recording + config_camera(); + + + pinMode(4, OUTPUT); // using 1 bit mode, shut off the Blinding Disk-Active Light + digitalWrite(4, LOW); + + pinMode(PIRpin, INPUT_PULLDOWN); // or PULLDOWN for active high + + newfile = 0; // no file is open // don't fiddle with this! + + recording = record_on_reboot; + + ready = 1; + + Serial.print("Camera Ready! Use 'http://"); + Serial.print(WiFi.localIP()); + Serial.println("' to connect"); + + +} + + +// +// 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 + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + digitalWrite(33, LOW); delay(150); + digitalWrite(33, HIGH); delay(150); + + delay(1000); + + digitalWrite(33, LOW); delay(500); + digitalWrite(33, HIGH); delay(500); + digitalWrite(33, LOW); delay(500); + digitalWrite(33, HIGH); delay(500); + 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; + + WiFi.disconnect(true); + WiFi.mode(WIFI_STA); + + WiFi.setHostname(devname); + //WiFi.printDiag(Serial); + WiFi.begin(ssid, password); + delay(1000); + while (WiFi.status() != WL_CONNECTED ) { + delay(500); + Serial.print("."); + if (connAttempts == 10) { + Serial.println("Cannot connect - try again"); + WiFi.begin(ssid, password); + WiFi.printDiag(Serial); + } + if (connAttempts == 20) { + Serial.println("Cannot connect - fail"); + + WiFi.printDiag(Serial); + return false; + } + connAttempts++; + } + + Serial.println("Internet connected"); + + //WiFi.printDiag(Serial); + + 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 = 10; + 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.println(ctime(&now)); + sprintf(localip, "%s", WiFi.localIP().toString().c_str()); + + return true; + +} + + +static esp_err_t init_sdcard() +{ + 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; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width = 1; // using 1 bit mode + //Serial.print("Slot config width should be 4 width: "); Serial.println(slot_config.width); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5, + }; + + //pinMode(4, OUTPUT); // using 1 bit mode, shut off the Blinding Disk-Active Light + //digitalWrite(4, LOW); + + sdmmc_card_t *card; + + Serial.println("Mounting SD 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)); + major_fail(); + } + sdmmc_card_print_info(stdout, card); + Serial.print("SD_MMC Begin: "); Serial.println(SD_MMC.begin()); // required by ftp system ?? +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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( ) { + + + PIRstatus = digitalRead(PIRpin); + + + if (PIRstatus == 1) { + + 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.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; + } + } + } + + + // we are recording, but no file is open + + if (newfile == 0 && recording == 1) { // open the file + + digitalWrite(33, HIGH); + newfile = 1; + start_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 (frames_so_far >= total_frames) { // we are done the recording + + Serial.println("Done capture for total frames!"); + + 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; + } else { + recording = 0; + PIRrecording = 0; + } + + } else 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; + } 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; + + fb_max = 7; // for vga and uxga + config.jpeg_quality = 8; + 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 + } + + for (int j = 0; j < 3; j++) { + do_fb(); // start the camera ... warm it up + delay(1); + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// start_avi - open the files and write in headers +// + +static esp_err_t start_avi() { + + Serial.println("Starting an avi "); + + config_camera(); + + time(&now); + localtime_r(&now, &timeinfo); + + strftime(strftime_buf, sizeof(strftime_buf), "%F_%H.%M.%S", &timeinfo); + + char fname[100]; + + if (framesize == 6) { + sprintf(fname, "/sdcard/%s_%s_vga_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 7) { + sprintf(fname, "/sdcard/%s_%s_svga_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 10) { + sprintf(fname, "/sdcard/%s_%s_uxga_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else if (framesize == 5) { + sprintf(fname, "/sdcard/%s_%s_cif_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } else { + Serial.println("Wrong framesize"); + sprintf(fname, "/sdcard/%s_%s_xxx_Q%d_I%d_L%d_S%d.avi", devname, strftime_buf, quality, capture_interval, xlength, xspeed); + } + + 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); + + if (framesize == 6) { + + 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 == 10) { + + 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 == 7) { + + 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 == 5) { + + 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; + + 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 ); + + } 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(); + size_t err = fwrite(fb_q[fb_out]->buf, 1, fb_q[fb_out]->len, avifile); + if (err == 0 ) { + Serial.println("Error on avi write"); + major_fail(); + } + 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); + + 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); + //Serial.println("Write done"); + //41 totalw = totalw + millis() - bw; + + //if (((fb_in + fb_max - fb_out) % fb_max) > 0 ) { + // Serial.print(((fb_in + fb_max - fb_out) % fb_max)); Serial.print(" "); + //} + + 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( totalp / frame_cnt ); + Serial.print("Average write time (ms) "); Serial.println( totalw / frame_cnt ); + Serial.print("Frames Skipped % "); Serial.println( 100.0 * skipped / 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(); + } + + sprintf(localip, "%s", WiFi.localIP().toString().c_str()); + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +// +// some globals for the loop() +// + +long wakeup; +long last_wakeup = 0; + + +void loop() +{ + + wakeup = millis(); + if (wakeup - last_wakeup > (14 * 60 * 1000) ) { // 14 minutes + last_wakeup = millis(); + + do_time(); + } + + ftpSrv.handleFTP(); + + //delay(1); + +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// + +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 ); + 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("Stopping previous recording"); + + 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[80]; + 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_length = capture_interval * total_frames; + + int new_framesize = s->status.framesize; + int new_quality = s->status.quality; + int new_repeat = 0; + int new_xspeed = 1; + int new_xlength = 3; + int new_gray = 0; + + /* + 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_length = 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); + } + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 10; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 7; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 6; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 5; + } else { + Serial.println("Only UXGA, 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 >= 10 && x <= 50) { // MINIMUM QUALITY 10 to save memory + new_quality = x; + } + + 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 <= 100) { + 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 == 1 ) { + new_gray = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => gray=%s", param); + } + + if (httpd_query_key_value(buf, "interval", param, sizeof(param)) == ESP_OK) { + + int x = atoi(param); + if (x >= 1 && x <= 180000) { // 180,000 ms = 3 min + new_interval = x; + } + ESP_LOGI(TAG, "Found URL query parameter => interval=%s", param); + } + } + } + + framesize = new_framesize; + capture_interval = new_interval; + xlength = new_length; + total_frames = new_length * 1000 / capture_interval; + repeat = new_repeat; + quality = new_quality; + xspeed = new_xspeed; + gray = new_gray; + + do_start("Starting a new AVI"); + httpd_resp_send(req, the_page, strlen(the_page)); + + recording = 1; + return ESP_OK; + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_start(char *the_message) { + + Serial.print("do_start "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +%s ESP32-CAM Video Recorder + + +

%s
ESP32-CAM Video Recorder %s


+

Message is %s


+ Recording = %d (1 is active)
+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (10 best to 50 worst)
+ Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d

+ +
+
+ + +)rawliteral"; + + + sprintf(the_page, msg, devname, devname, vernum, the_message, recording, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray); + //Serial.println(strlen(msg)); + +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_stop(char *the_message) { + + 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


+

Message is %s


+

http://%s/

+

http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0

+

VGA 2 fps, for 30 minutes repeat, 30x playback

+

UXGA 1 sec per frame, for 30 minutes repeat, 30x playback

+

UXGA 2 fps for 30 minutes repeat, 15x playback

+

CIF 20 fps second for 30 minutes repeat

+
+ +)rawliteral"; + + sprintf(the_page, msg, devname, devname, vernum, the_message, localip, localip, localip, localip, localip, localip, localip, localip); + +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +void do_status(char *the_message) { + + Serial.print("do_status "); Serial.println(the_message); + + const char msg[] PROGMEM = R"rawliteral( + + + + +%s ESP32-CAM Video Recorder + + +

%s
ESP32-CAM Video Recorder %s
%s


+

Message is %s


+ Total SD Space is %d MB, Used SD Space is %d MB
+ Recording = %d (1 is active)
+ Frame %d of %d, Skipped %d

+ Capture Interval = %d ms
+ Length = %d seconds
+ Quality = %d (10 best to 50 worst)
+ Framesize = %d (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Playback Speed = %d
+ Gray = %d

+ Commands as follows for your ESP's ip address:
+

http://%s/

+

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

+

ftp://%s/

+ Username: esp, Password: esp ... to download the files

+ Red LED on back of ESP will flash with every frame (or skipped frames), and flash SOS if camera or sd card not working.
+ +
+Check camera position with the frame below
+Refresh page for more.
+
+
+ +)rawliteral"; + + time(&now); + const char *strdate = ctime(&now); + + //Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); + //Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); + + int tot = SD_MMC.totalBytes() / (1024 * 1024); + int use = SD_MMC.usedBytes() / (1024 * 1024); + + //Serial.print(strlen(msg)); Serial.print(" "); + + sprintf(the_page, msg, devname, devname, vernum, strdate, the_message, tot, use, recording, frames_so_far, total_frames, skipped, capture_interval, capture_interval * total_frames / 1000, quality, framesize, repeat, xspeed, gray, localip, localip, localip, localip, localip, localip); + + //Serial.println(strlen(the_page)); +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// +static esp_err_t index_handler(httpd_req_t *req) { + + do_status("Refresh Status"); + httpd_resp_send(req, the_page, strlen(the_page)); + return ESP_OK; +} + +void startCameraServer() { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + + 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 + }; + + 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); + } + + Serial.println("Camera http started"); +} diff --git a/v89/ESP32FtpServer.cpp b/old/v86/ESP32FtpServer.cpp similarity index 96% rename from v89/ESP32FtpServer.cpp rename to old/v86/ESP32FtpServer.cpp index c9323aa..55da8eb 100644 --- a/v89/ESP32FtpServer.cpp +++ b/old/v86/ESP32FtpServer.cpp @@ -1,1173 +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; -} +/* + * 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/v94/ESP32FtpServer.h b/old/v86/ESP32FtpServer.h similarity index 97% rename from v94/ESP32FtpServer.h rename to old/v86/ESP32FtpServer.h index 23869e5..bb6d67d 100644 --- a/v94/ESP32FtpServer.h +++ b/old/v86/ESP32FtpServer.h @@ -1,116 +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 + +/* +* 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/v86/TimeLapseAvi86x.ino b/old/v86/TimeLapseAvi86x.ino similarity index 96% rename from v86/TimeLapseAvi86x.ino rename to old/v86/TimeLapseAvi86x.ino index a70e2ca..55a1d23 100644 --- a/v86/TimeLapseAvi86x.ino +++ b/old/v86/TimeLapseAvi86x.ino @@ -1,2271 +1,2271 @@ -#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 86 - Jun 30, 2020 TimeLapseAvi86x.ino - - - redo camera scheduler to reduce frame skips with slight delays between frames - - move more processing to separate priority tasks, and remove from idle loop() - - most tasks suspened waiting for events, rather than loopong checking for events, ... except ftp which still loops wating for ftp requests - - added a sd card snapshot jpg at beginning of every movie - - added a telegram.org message with opening picture and info about diskspace and rssi to follow camera activity on your computer or phone - - added deepsleep feature to wake on PIR, and then deepsleep after movie is recorded - - added touch sensor on pin12 to enable/disable the pir sensor - - added more careful setup of difficult pins 12, 13, and 4 - used for SD and re-used for PIR, Touch, and Blinding Disk-Active Light - - added brownout handler to close files on brownout, which didn't work, but at least I can deepsleep to prevent multiple brownout reboots - - inside a brownout handler, you have only 300ms and you cannot access wifi, sd, or flash, ... so cannot close files, or send message - - re-used pin 4 Blinding Disk-Active Light to blink gently at beginning of movie, and at a Touch - ironically, also turns on during Brownout ;-) - - added several functions to enable / disable pir or bot using internet - http://desklens.local/bot_enable - http://desklens.local/bot_disable - http://desklens.local/pir_enable - http://desklens.local/pir_disable - - moved many settings to a separate file "settings.h" so you edit that, rather than digging through the main file to set your wifi password, startup defaults, - and enable/disable internet, pir, telegram, etc - - not super-elegant code ... still haven't written the avi writer into a nice library - - read comment on rtc_cntl.h below which may or may not be updated in the esp32 board library - links and info below - Hardware - - to use PIR function, put an active high PIR or microwave on pin 12 with a 10k resistor (brown,black,orange) between pin 12 and PIR outputto avoid antagonizing sd card - - to use Touch function, put a wire (with optional metal touch point) on pin 13 and touch it to enable/disable pir - - Blinding Disk-Active Light will give little blink during a touch, or when starting a recording - - red led on back with blink with every frame if you have that enabled in settings - -*/ - -/* - Using library ESP32 at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32 - Using library WiFi at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi - Using library WiFiClientSecure at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFiClientSecure - Using library ArduinoJson at version 6.15.2 in folder: C:\Users\James\Documents\Arduino\libraries\ArduinoJson - Using library ESPmDNS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESPmDNS - Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC - Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS - Using library HTTPClient at version 1.2 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\HTTPClient -*/ - - -static const char vernum[] = "v86"; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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 * capture_interval / 1000; -int recording = 0; -int PIRstatus = 0; -int PIRrecording = 0; -int ready = 0; - -//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE -#include "esp_log.h" -#include "esp_http_server.h" -#include "esp_camera.h" - -#include -#include -#include "UniversalTelegramBot.h" - -WiFiClientSecure client; - -UniversalTelegramBot bot(BOTtoken, client); - -int diskspeed = 0; -char fname[100]; -int send_a_telegram = 0; -int Wait_for_bot = 0; - -#include - -#include "ESP32FtpServer.h" -#include - -FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial - -// 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 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 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, 0x38, 0x36, 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); - } - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); - - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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(); - 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 x[1]; - - 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" - -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); - - touchAttachInterrupt(T5, get_touch5, 30); - Serial.print("Touch T5 = "); - for (int i = 0; i < 5; i++) { - Serial.print( touchRead(T5) ); Serial.print(", "); - } - Serial.println(" "); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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 -// - -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]; - - if (framesize == 6) { - 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 == 7) { - 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 == 10) { - 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 == 5) { - 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); - - esp_camera_fb_return(fb); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// send_photo_telegram - send an opening frame to telegram to see on phone or computer -// -// - this often fails if run at same time as a recording due to heap -// - the telegram ssl connection needs about 60k, the the sd write fuctions needs heap as well -// - -static esp_err_t send_photo_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, 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("<"); - } - - - esp_camera_fb_return(fb); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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 -// -// - turns on Blinding Disk-Active light and deepsleeps at the end -// - not the correct action if you are using a non-protected lipo battery, but does -// prevent multiple reboots on a weak battery, and alets the human with the bright led -// -// - mostly included as information, as it was a lot of work and didn't ultimately work to close -// the avi files - -void IRAM_ATTR low_voltage_save(void *arg) { - Serial.print("\nJZ low voltage handler\nStarting at "); - long start_of_inter = millis(); - Serial.println(start_of_inter); - time(&now); - const char *strdate = ctime(&now); - Serial.println(strdate); - - //recording = 0; - //Serial.println("\nstopping recording"); - - Serial.print("low volt, 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("\nBrown 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); - - for (int i = 0; i < 1000000; i++) { - Serial.print(millis() - start_of_inter); Serial.print(" ms, i = "); Serial.println(i); - 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 - - esp_deep_sleep_start(); - } - } - - Serial.println("... switching to system shutdown ..."); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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; - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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" - -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("-------------------------------------"); - - ++bootCount; - Serial.println("Boot number: " + String(bootCount)); - print_wakeup_reason(); - - 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"); - } - } - //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) startCameraServer(); - - // zzz username and password for ftp server - - //plm print_ram(); delay(2000); - Serial.println("Starting ftp ..."); - - if (Internet_Enabled) ftpSrv.begin("esp", "esp"); - - 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); - - - if (Internet_Enabled) { - xTaskCreatePinnedToCore( - codeForFtpTask, - "FtpTask", - 4096, // heap - NULL, - 4, // prio higher than 1 - &FtpTask, - 0); // on cpu 0 - - delay(20); - } - //plm print_ram(); delay(2000); - Serial.println("Starting camera ..."); - - recording = 0; // we are NOT recording - config_camera(); - - setupinterrupts(); - - 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); - //plm print_ram(); delay(2000); -} - - -// -// 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("\nBrown regsiter was (in hex)"); Serial.println(brown_reg_temp, HEX); - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector - - 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 (!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 - 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( ) { - - 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; - } - } - } - } - - // we are recording, but no file is open - - if (newfile == 0 && recording == 1) { // open the file - - digitalWrite(33, HIGH); - newfile = 1; - save_photo_dated(); // save a photo as jpg on sd - - if (EnableBOT == 1 && Internet_Enabled == 1) { // if BOT is enabled wait to send it ... could be several seconds (5 or 10) - 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(" "); - 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; - - 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 < 3; j++) { - do_fb(); // start the camera ... warm it up - delay(1); - } -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// start_avi - open the files and write in headers -// - - -static esp_err_t start_avi() { - - Serial.println("Starting an avi "); - - //plm print_ram(); - - 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); - - // strftime(inBuff, inBuffLen, "/%Y%m%d/%Y%m%d_%H%M%S", localtime(&currEpoch)); - - if (framesize == 6) { - 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 == 7) { - 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 == 10) { - 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 == 5) { - 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); - - if (framesize == 6) { - - 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 == 10) { - - 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 == 7) { - - 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 == 5) { - - 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; - - 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); - } - - 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); - - 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); - - 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 / 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; - } - 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); - 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(); - } - } - } - count_loop++; - wakeup = millis(); - if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes - last_wakeup = millis(); - do_time(); - - //plm print_ram(); - } - - 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 - send_photo_telegram(); - Wait_for_bot = 0; - } - } - - 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_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_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_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_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_xlength = capture_interval * total_frames; - - int new_framesize = s->status.framesize; - int new_quality = s->status.quality; - int new_repeat = repeat; - int new_xspeed = xspeed; - int new_xlength = xlength; - int new_gray = gray; - - /* - 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); - } - if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { - if (strcmp(new_res, "UXGA") == 0) { - new_framesize = 10; - } else if (strcmp(new_res, "SVGA") == 0) { - new_framesize = 7; - } else if (strcmp(new_res, "VGA") == 0) { - new_framesize = 6; - } else if (strcmp(new_res, "CIF") == 0) { - new_framesize = 5; - } else { - Serial.println("Only UXGA, 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 >= 10 && x <= 50) { // MINIMUM QUALITY 10 to save memory - new_quality = x; - } - - 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 == 1 ) { - new_gray = x; - } - - ESP_LOGI(TAG, "Found URL query parameter => gray=%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; - repeat = new_repeat; - quality = new_quality; - xspeed = new_xspeed; - gray = new_gray; - - 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); - - 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Speed = %d
- Gray = %d

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

%s
ESP32-CAM Video Recorder %s


-

%s


-

http://%s/

-

http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0

-

VGA 2 fps, for 30 minutes repeat, 15x playback

-

UXGA 1 sec per frame, for 30 minutes repeat, 30x playback

-

UXGA 2 fps for 30 minutes repeat, 15x playback

-

UXGA 10 sec per frame for 1 hour x300 repeat, Q12

-

UXGA 5 sec per frame for 1 hour x150 repeat

-

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
- 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d, Playback Speed = %d, Gray = %d
-
-

http://%s/

-

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

-

ftp://%s/ ... Username: esp, Password: esp

-
- -
-
- -)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, capture_interval, ms_per_frame, avg_frame_wrt, capture_interval * total_frames / 1000, - quality, framesize, repeat, xspeed, gray, 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(); - //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 - }; - - - 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); - } - - Serial.println("Camera http started"); -} +#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 86 - Jun 30, 2020 TimeLapseAvi86x.ino + + - redo camera scheduler to reduce frame skips with slight delays between frames + - move more processing to separate priority tasks, and remove from idle loop() + - most tasks suspened waiting for events, rather than loopong checking for events, ... except ftp which still loops wating for ftp requests + - added a sd card snapshot jpg at beginning of every movie + - added a telegram.org message with opening picture and info about diskspace and rssi to follow camera activity on your computer or phone + - added deepsleep feature to wake on PIR, and then deepsleep after movie is recorded + - added touch sensor on pin12 to enable/disable the pir sensor + - added more careful setup of difficult pins 12, 13, and 4 - used for SD and re-used for PIR, Touch, and Blinding Disk-Active Light + - added brownout handler to close files on brownout, which didn't work, but at least I can deepsleep to prevent multiple brownout reboots + - inside a brownout handler, you have only 300ms and you cannot access wifi, sd, or flash, ... so cannot close files, or send message + - re-used pin 4 Blinding Disk-Active Light to blink gently at beginning of movie, and at a Touch - ironically, also turns on during Brownout ;-) + - added several functions to enable / disable pir or bot using internet + http://desklens.local/bot_enable + http://desklens.local/bot_disable + http://desklens.local/pir_enable + http://desklens.local/pir_disable + - moved many settings to a separate file "settings.h" so you edit that, rather than digging through the main file to set your wifi password, startup defaults, + and enable/disable internet, pir, telegram, etc + - not super-elegant code ... still haven't written the avi writer into a nice library + - read comment on rtc_cntl.h below which may or may not be updated in the esp32 board library - links and info below + Hardware + - to use PIR function, put an active high PIR or microwave on pin 12 with a 10k resistor (brown,black,orange) between pin 12 and PIR outputto avoid antagonizing sd card + - to use Touch function, put a wire (with optional metal touch point) on pin 13 and touch it to enable/disable pir + - Blinding Disk-Active Light will give little blink during a touch, or when starting a recording + - red led on back with blink with every frame if you have that enabled in settings + +*/ + +/* + Using library ESP32 at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32 + Using library WiFi at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi + Using library WiFiClientSecure at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFiClientSecure + Using library ArduinoJson at version 6.15.2 in folder: C:\Users\James\Documents\Arduino\libraries\ArduinoJson + Using library ESPmDNS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESPmDNS + Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC + Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS + Using library HTTPClient at version 1.2 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\HTTPClient +*/ + + +static const char vernum[] = "v86"; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// 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 * capture_interval / 1000; +int recording = 0; +int PIRstatus = 0; +int PIRrecording = 0; +int ready = 0; + +//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#include "esp_log.h" +#include "esp_http_server.h" +#include "esp_camera.h" + +#include +#include +#include "UniversalTelegramBot.h" + +WiFiClientSecure client; + +UniversalTelegramBot bot(BOTtoken, client); + +int diskspeed = 0; +char fname[100]; +int send_a_telegram = 0; +int Wait_for_bot = 0; + +#include + +#include "ESP32FtpServer.h" +#include + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + +// 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 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 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, 0x38, 0x36, 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); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); + + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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(); + 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 x[1]; + + 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" + +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); + + touchAttachInterrupt(T5, get_touch5, 30); + Serial.print("Touch T5 = "); + for (int i = 0; i < 5; i++) { + Serial.print( touchRead(T5) ); Serial.print(", "); + } + Serial.println(" "); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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"); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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 +// + +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]; + + if (framesize == 6) { + 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 == 7) { + 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 == 10) { + 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 == 5) { + 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); + + esp_camera_fb_return(fb); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// send_photo_telegram - send an opening frame to telegram to see on phone or computer +// +// - this often fails if run at same time as a recording due to heap +// - the telegram ssl connection needs about 60k, the the sd write fuctions needs heap as well +// + +static esp_err_t send_photo_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, 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("<"); + } + + + esp_camera_fb_return(fb); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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 +// +// - turns on Blinding Disk-Active light and deepsleeps at the end +// - not the correct action if you are using a non-protected lipo battery, but does +// prevent multiple reboots on a weak battery, and alets the human with the bright led +// +// - mostly included as information, as it was a lot of work and didn't ultimately work to close +// the avi files + +void IRAM_ATTR low_voltage_save(void *arg) { + Serial.print("\nJZ low voltage handler\nStarting at "); + long start_of_inter = millis(); + Serial.println(start_of_inter); + time(&now); + const char *strdate = ctime(&now); + Serial.println(strdate); + + //recording = 0; + //Serial.println("\nstopping recording"); + + Serial.print("low volt, 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("\nBrown 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); + + for (int i = 0; i < 1000000; i++) { + Serial.print(millis() - start_of_inter); Serial.print(" ms, i = "); Serial.println(i); + 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 + + esp_deep_sleep_start(); + } + } + + Serial.println("... switching to system shutdown ..."); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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; + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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" + +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("-------------------------------------"); + + ++bootCount; + Serial.println("Boot number: " + String(bootCount)); + print_wakeup_reason(); + + 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"); + } + } + //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) startCameraServer(); + + // zzz username and password for ftp server + + //plm print_ram(); delay(2000); + Serial.println("Starting ftp ..."); + + if (Internet_Enabled) ftpSrv.begin("esp", "esp"); + + 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); + + + if (Internet_Enabled) { + xTaskCreatePinnedToCore( + codeForFtpTask, + "FtpTask", + 4096, // heap + NULL, + 4, // prio higher than 1 + &FtpTask, + 0); // on cpu 0 + + delay(20); + } + //plm print_ram(); delay(2000); + Serial.println("Starting camera ..."); + + recording = 0; // we are NOT recording + config_camera(); + + setupinterrupts(); + + 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); + //plm print_ram(); delay(2000); +} + + +// +// 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("\nBrown regsiter was (in hex)"); Serial.println(brown_reg_temp, HEX); + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector + + 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 (!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 + 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( ) { + + 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; + } + } + } + } + + // we are recording, but no file is open + + if (newfile == 0 && recording == 1) { // open the file + + digitalWrite(33, HIGH); + newfile = 1; + save_photo_dated(); // save a photo as jpg on sd + + if (EnableBOT == 1 && Internet_Enabled == 1) { // if BOT is enabled wait to send it ... could be several seconds (5 or 10) + 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(" "); + 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; + + 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 < 3; j++) { + do_fb(); // start the camera ... warm it up + delay(1); + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// start_avi - open the files and write in headers +// + + +static esp_err_t start_avi() { + + Serial.println("Starting an avi "); + + //plm print_ram(); + + 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); + + // strftime(inBuff, inBuffLen, "/%Y%m%d/%Y%m%d_%H%M%S", localtime(&currEpoch)); + + if (framesize == 6) { + 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 == 7) { + 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 == 10) { + 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 == 5) { + 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); + + if (framesize == 6) { + + 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 == 10) { + + 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 == 7) { + + 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 == 5) { + + 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; + + 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); + } + + 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); + + 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); + + 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 / 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; + } + 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); + 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(); + } + } + } + count_loop++; + wakeup = millis(); + if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes + last_wakeup = millis(); + do_time(); + + //plm print_ram(); + } + + 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 + send_photo_telegram(); + Wait_for_bot = 0; + } + } + + 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_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_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_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_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_xlength = capture_interval * total_frames; + + int new_framesize = s->status.framesize; + int new_quality = s->status.quality; + int new_repeat = repeat; + int new_xspeed = xspeed; + int new_xlength = xlength; + int new_gray = gray; + + /* + 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); + } + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 10; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 7; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 6; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 5; + } else { + Serial.println("Only UXGA, 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 >= 10 && x <= 50) { // MINIMUM QUALITY 10 to save memory + new_quality = x; + } + + 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 == 1 ) { + new_gray = x; + } + + ESP_LOGI(TAG, "Found URL query parameter => gray=%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; + repeat = new_repeat; + quality = new_quality; + xspeed = new_xspeed; + gray = new_gray; + + 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); + + 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d

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

%s
ESP32-CAM Video Recorder %s


+

%s


+

http://%s/

+

http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0

+

VGA 2 fps, for 30 minutes repeat, 15x playback

+

UXGA 1 sec per frame, for 30 minutes repeat, 30x playback

+

UXGA 2 fps for 30 minutes repeat, 15x playback

+

UXGA 10 sec per frame for 1 hour x300 repeat, Q12

+

UXGA 5 sec per frame for 1 hour x150 repeat

+

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
+ 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d, Playback Speed = %d, Gray = %d
+
+

http://%s/

+

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

+

ftp://%s/ ... Username: esp, Password: esp

+
+ +
+
+ +)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, capture_interval, ms_per_frame, avg_frame_wrt, capture_interval * total_frames / 1000, + quality, framesize, repeat, xspeed, gray, 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(); + //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 + }; + + + 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); + } + + Serial.println("Camera http started"); +} diff --git a/v86/UniversalTelegramBot.cpp b/old/v86/UniversalTelegramBot.cpp similarity index 96% rename from v86/UniversalTelegramBot.cpp rename to old/v86/UniversalTelegramBot.cpp index 1bf1229..24d45bd 100644 --- a/v86/UniversalTelegramBot.cpp +++ b/old/v86/UniversalTelegramBot.cpp @@ -1,952 +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(100); - // Host header - client->print(F("Host:")); - client->println(HOST); - delay(100); - // JSON content type - client->println(F("Content-Type: application/json")); - delay(100); - - // Content length - int length = measureJson(payload); - client->print(F("Content-Length:")); - client->println(length); - delay(100); - // End of headers - client->println(); - // POST message body - String out; - serializeJson(payload, out); - - client->println(out); - delay(100); - - 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(100); //jz - client->println(F("Accept: */*")); - Serial.print("*") ; delay(100); //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(100); //jz - client->println(""); - Serial.print("*") ; delay(100); //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[512]; //jz 512 - int count = 0; - char ch; - while (moreDataAvailableCallback()) { - buffer[count] = getNextByteCallback(); - count++; - if (count == 512) { //jz 512 - // yield(); -#ifdef _debug - //Serial.println(F("Sending binary photo full buffer")); -#endif - client->write((const uint8_t *)buffer, 512); //jz 512 - Serial.print("*") ; delay(100); //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(100); //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(100); //jz - client->println(F("Accept: */*")); - Serial.print("*") ; delay(100); //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(100); //jz - client->println(""); - Serial.print("*") ; delay(100); //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[512]; - int count = 0; - char ch; - while (moreDataAvailableCallback()) { - buffer[count] = getNextByteCallback(); - count++; - if (count == 512) { - // yield(); -#ifdef _debug - //Serial.println(F("Sending binary photo full buffer")); -#endif - client->write((const uint8_t *)buffer, 512); - Serial.print("*") ; delay(100); //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(100); //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(); - } -} +/* + 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(100); + // Host header + client->print(F("Host:")); + client->println(HOST); + delay(100); + // JSON content type + client->println(F("Content-Type: application/json")); + delay(100); + + // Content length + int length = measureJson(payload); + client->print(F("Content-Length:")); + client->println(length); + delay(100); + // End of headers + client->println(); + // POST message body + String out; + serializeJson(payload, out); + + client->println(out); + delay(100); + + 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(100); //jz + client->println(F("Accept: */*")); + Serial.print("*") ; delay(100); //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(100); //jz + client->println(""); + Serial.print("*") ; delay(100); //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[512]; //jz 512 + int count = 0; + char ch; + while (moreDataAvailableCallback()) { + buffer[count] = getNextByteCallback(); + count++; + if (count == 512) { //jz 512 + // yield(); +#ifdef _debug + //Serial.println(F("Sending binary photo full buffer")); +#endif + client->write((const uint8_t *)buffer, 512); //jz 512 + Serial.print("*") ; delay(100); //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(100); //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(100); //jz + client->println(F("Accept: */*")); + Serial.print("*") ; delay(100); //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(100); //jz + client->println(""); + Serial.print("*") ; delay(100); //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[512]; + int count = 0; + char ch; + while (moreDataAvailableCallback()) { + buffer[count] = getNextByteCallback(); + count++; + if (count == 512) { + // yield(); +#ifdef _debug + //Serial.println(F("Sending binary photo full buffer")); +#endif + client->write((const uint8_t *)buffer, 512); + Serial.print("*") ; delay(100); //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(100); //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/v86/UniversalTelegramBot.h b/old/v86/UniversalTelegramBot.h similarity index 97% rename from v86/UniversalTelegramBot.h rename to old/v86/UniversalTelegramBot.h index 6ce38e0..55fc15f 100644 --- a/v86/UniversalTelegramBot.h +++ b/old/v86/UniversalTelegramBot.h @@ -1,123 +1,123 @@ -/* -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 = 10000; //jz = 1500; - -private: - // JsonObject * parseUpdates(String response); - String _token; - Client *client; - void closeClient(); - const int maxMessageLength = 1500; //was 1500 - bool processResult(JsonObject result, int messageIndex); -}; - -#endif +/* +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 = 10000; //jz = 1500; + +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/v86/readme.md b/old/v86/readme.md similarity index 100% rename from v86/readme.md rename to old/v86/readme.md diff --git a/v89/rtc_cntl.h b/old/v86/rtc_cntl.h similarity index 97% rename from v89/rtc_cntl.h rename to old/v86/rtc_cntl.h index 1fd303a..9d04c09 100644 --- a/v89/rtc_cntl.h +++ b/old/v86/rtc_cntl.h @@ -1,64 +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 +// ... 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/v86/settings.h b/old/v86/settings.h similarity index 98% rename from v86/settings.h rename to old/v86/settings.h index fd97108..04b5081 100644 --- a/v86/settings.h +++ b/old/v86/settings.h @@ -1,53 +1,53 @@ -static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames - -// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here -#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT - -// 1 for blink red led with every sd card write, at your frame rate -// 0 for blink only for skipping frames and SOS if camera or sd is broken -#define BlinkWithWrite 1 - -// EDIT ssid and password -const char* ssid = "yourwifi"; -const char* password = "youwifipassword"; - -// 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 - -// here are 2 sets of startup parameters - -// VGA 10 fps for 30 minutes, and repeat, play at real time - -int framesize = 6; // 10 UXGA, 7 SVGA, 6 VGA, 5 CIF -int repeat = 100; // repeat same movie this many times -int xspeed = 1; // playback speed - realtime is 1, or 300 means playpack 30 fps of frames recorded 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 = 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 = 10; // 10 UXGA, 7 SVGA, 6 VGA, 5 CIF -int repeat = 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 = 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 = 10000; // milli-seconds between frames -volatile int total_frames = 360; // how many frames - length of movie is total_frames x capture_interval -*/ - -// enable the www.telegram.org BOT - it sends a snapshot to your telegram every time it starts a video -// https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot for more info about esp32 telegram -// 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 - -RTC_DATA_ATTR int EnableBOT = 0; // 1 to enable -#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org -#define BOTme "1234567890" // these are fake +static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames + +// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here +#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT + +// 1 for blink red led with every sd card write, at your frame rate +// 0 for blink only for skipping frames and SOS if camera or sd is broken +#define BlinkWithWrite 1 + +// EDIT ssid and password +const char* ssid = "yourwifi"; +const char* password = "youwifipassword"; + +// 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 + +// here are 2 sets of startup parameters + +// VGA 10 fps for 30 minutes, and repeat, play at real time + +int framesize = 6; // 10 UXGA, 7 SVGA, 6 VGA, 5 CIF +int repeat = 100; // repeat same movie this many times +int xspeed = 1; // playback speed - realtime is 1, or 300 means playpack 30 fps of frames recorded 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 = 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 = 10; // 10 UXGA, 7 SVGA, 6 VGA, 5 CIF +int repeat = 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 = 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 = 10000; // milli-seconds between frames +volatile int total_frames = 360; // how many frames - length of movie is total_frames x capture_interval +*/ + +// enable the www.telegram.org BOT - it sends a snapshot to your telegram every time it starts a video +// https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot for more info about esp32 telegram +// 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 + +RTC_DATA_ATTR int EnableBOT = 0; // 1 to enable +#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org +#define BOTme "1234567890" // these are fake diff --git a/v86/v86-Telegram.jpg b/old/v86/v86-Telegram.jpg similarity index 100% rename from v86/v86-Telegram.jpg rename to old/v86/v86-Telegram.jpg diff --git a/v86/v86-status.jpg b/old/v86/v86-status.jpg similarity index 100% rename from v86/v86-status.jpg rename to old/v86/v86-status.jpg diff --git a/v86/v86-stop.jpg b/old/v86/v86-stop.jpg similarity index 100% rename from v86/v86-stop.jpg rename to old/v86/v86-stop.jpg diff --git a/v86/v86.jpg b/old/v86/v86.jpg similarity index 100% rename from v86/v86.jpg rename to old/v86/v86.jpg diff --git a/v94/ESP32FtpServer.cpp b/old/v89/ESP32FtpServer.cpp similarity index 96% rename from v94/ESP32FtpServer.cpp rename to old/v89/ESP32FtpServer.cpp index c9323aa..55da8eb 100644 --- a/v94/ESP32FtpServer.cpp +++ b/old/v89/ESP32FtpServer.cpp @@ -1,1173 +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; -} +/* + * 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/v86/ESP32FtpServer.h b/old/v89/ESP32FtpServer.h similarity index 97% rename from v86/ESP32FtpServer.h rename to old/v89/ESP32FtpServer.h index 23869e5..bb6d67d 100644 --- a/v86/ESP32FtpServer.h +++ b/old/v89/ESP32FtpServer.h @@ -1,116 +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 + +/* +* 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/v89/TimeLapseAvi89x.ino b/old/v89/TimeLapseAvi89x.ino similarity index 96% rename from v89/TimeLapseAvi89x.ino rename to old/v89/TimeLapseAvi89x.ino index 82eb6b4..cf2bd70 100644 --- a/v89/TimeLapseAvi89x.ino +++ b/old/v89/TimeLapseAvi89x.ino @@ -1,2460 +1,2460 @@ -#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 89 - Jul 13, 2020 - - bot/pir enable/diable on web - - less re-progs of camera - - store settings in eprom, so it reboots back to where it was - - more pictures before movie starts to stablize exposure - - Version 86 - Jun 30, 2020 - - redo camera scheduler to reduce frame skips with slight delays between frames - - move more processing to separate priority tasks, and remove from idle loop() - - most tasks suspened waiting for events, rather than loopong checking for events, ... except ftp which still loops wating for ftp requests - - added a sd card snapshot jpg at beginning of every movie - - added a telegram.org message with opening picture and info about diskspace and rssi to follow activity on camera on your computer or phone - - added deepsleep feature to wake on PIR, and then deepsleep after movie is recorded - - added touch sensor on pin12 to enable/disable the pir sensor - - added more careful setup of difficult pins 12, 13, and 4 - used for SD and re-used for PIR, Touch, and Blinding Disk-Active Light - - added brownout handler to close files on brownout, which didn't work, but at least I can deepsleep to prevent multiple brownout reboots - - inside a brownout handler, you have only 300ms and you cannot access wifi, sd, or flash, ... so cannot close files, or send message - - re-used pin 4 Blinding Disk-Active Light to blink gently at beginning of movie, and at a Touch - ironically, also turns on during Brownout ;-) - - added several functions to enable / disable pir or bot using internet - http://desklens.local/bot_enable - http://desklens.local/bot_disable - http://desklens.local/pir_enable - http://desklens.local/pir_disable - - moved many settings to a separate file "settings.h" so you edit that, rather than digging through the main file to set your wifi password, startup defaults, - and enable/disable internet, pir, telegram, etc - - not super-elegant code ... still haven't written the avi writer into a nice library - - read comment on rtc_cntl.h below which may or may not be updated in the esp32 board library - links and info below - Hardware - - to use PIR function, put an active high PIR or microwave on pin 12 with a 10k resistor (brown,black,orange) to avoid antagonizing sd card - - to use Touch function, put a wire (with optional metal touch point) on pin 13 and touch it to enable/disable pir - - Blinding Disk-Active Light will give little blink during a touch, or when starting a recording - - red led on back with blink with every frame if you have that enabled in settings - -*/ - -/* -Using library ESP32 at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32 -Using library EEPROM at version 1.0.3 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\EEPROM -Using library WiFi at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi -Using library WiFiClientSecure at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFiClientSecure -Using library ArduinoJson at version 6.15.2 in folder: C:\Users\James\Documents\Arduino\libraries\ArduinoJson -Using library ESPmDNS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESPmDNS -Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC -Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS -Using library HTTPClient at version 1.2 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\HTTPClient -*/ - - -static const char vernum[] = "v89"; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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" - -#include -#include -#include "UniversalTelegramBot.h" - -WiFiClientSecure client; - -UniversalTelegramBot bot(BOTtoken, client); - -int diskspeed = 0; -char fname[100]; -int send_a_telegram = 0; -int Wait_for_bot = 0; - -#include - -#include "ESP32FtpServer.h" -#include - -FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial - -// 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 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 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, 0x38, 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); - } - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); - - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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(); - 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 x[1]; - - 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" - -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); - - touchAttachInterrupt(T5, get_touch5, 30); - Serial.print("Touch T5 = "); - for (int i = 0; i < 5; i++) { - Serial.print( touchRead(T5) ); Serial.print(", "); - } - Serial.println(" "); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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]; - - if (framesize == 6) { - 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 == 7) { - 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 == 10) { - 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 == 5) { - 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); - ///// - - ///// - 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("<"); - } - - } - //esp_camera_fb_return(fb); - ///// - esp_camera_fb_return(fb); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// send_photo_telegram - send an opening frame to telegram to see on phone or computer -// -// - this often fails if run at same time as a recording due to heap -// - the telegram ssl connection needs about 60k, the the sd write fuctions needs heap as well -// - -static esp_err_t send_photo_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, 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("<"); - } - - - esp_camera_fb_return(fb); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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 -// -// - turns on Blinding Disk-Active light and deepsleeps at the end -// - not the correct action if you are using a non-protected lipo battery, but does -// prevent multiple reboots on a weak battery, and alets the human with the bright led -// -// - mostly included as information, as it was a lot of work and didn't ultimately work to close -// the avi files - -void IRAM_ATTR low_voltage_save(void *arg) { - Serial.print("\nJZ low voltage handler\nStarting at "); - long start_of_inter = millis(); - Serial.println(start_of_inter); - time(&now); - const char *strdate = ctime(&now); - Serial.println(strdate); - - //recording = 0; - //Serial.println("\nstopping recording"); - - Serial.print("low volt, 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("\nBrown 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); - - for (int i = 0; i < 1000000; i++) { - Serial.print(millis() - start_of_inter); Serial.print(" ms, i = "); Serial.println(i); - 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 - - esp_deep_sleep_start(); - } - } - - Serial.println("... switching to system shutdown ..."); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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; - } -} - -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 == 271) { - 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 = 271; - 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" - -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("-------------------------------------"); - - ++bootCount; - Serial.println("Boot number: " + String(bootCount)); - print_wakeup_reason(); - - 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) startCameraServer(); - - // zzz username and password for ftp server - - //plm print_ram(); delay(2000); - Serial.println("Starting ftp ..."); - - if (Internet_Enabled) ftpSrv.begin("esp", "esp"); - - 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); - - - if (Internet_Enabled) { - xTaskCreatePinnedToCore( - codeForFtpTask, - "FtpTask", - 4096, // heap - NULL, - 4, // prio higher than 1 - &FtpTask, - 0); // on cpu 0 - - delay(20); - } - //plm print_ram(); delay(2000); - Serial.println("Starting camera ..."); - - recording = 0; // we are NOT recording - config_camera(); - - setupinterrupts(); - - 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); - //plm print_ram(); delay(2000); -} - - -// -// 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("\nBrown regsiter was (in hex)"); Serial.println(brown_reg_temp, HEX); - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector - - 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 (!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 - 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( ) { - - 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; - } - } - } - } - - // 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(" "); - 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; - - 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(2); - } -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); - - if (framesize == 6) { - 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 == 7) { - 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 == 10) { - 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 == 5) { - 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); - - if (framesize == 6) { - - 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 == 10) { - - 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 == 7) { - - 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 == 5) { - - 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; - - 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); - } - - 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); - - 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); - - 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 / 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; - } - 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); - 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(); - } - } - } - count_loop++; - wakeup = millis(); - if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes - last_wakeup = millis(); - do_time(); - - //plm print_ram(); - } - - 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; - } - } - - 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); - } - if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { - if (strcmp(new_res, "UXGA") == 0) { - new_framesize = 10; - } else if (strcmp(new_res, "SVGA") == 0) { - new_framesize = 7; - } else if (strcmp(new_res, "VGA") == 0) { - new_framesize = 6; - } else if (strcmp(new_res, "CIF") == 0) { - new_framesize = 5; - } else { - Serial.println("Only UXGA, 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 >= 10 && x <= 50) { // MINIMUM QUALITY 10 to save memory - new_quality = x; - } - - 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 => gray=%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 => gray=%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); - - 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Speed = %d
- Gray = %d
- PIR = %d
- BOT = %d

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

%s
ESP32-CAM Video Recorder %s


-

%s


-

http://%s/

-
http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0&pir=1&bot=1 -
VGA 2 fps, for 30 minutes repeat, 15x playback -
UXGA 1 sec per frame, for 30 minutes repeat, 30x playback, with bot -
UXGA 2 fps for 30 minutes repeat, 15x playback -
UXGA 10 sec per frame for 1 hour x300 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
- 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d, Playback Speed = %d, Gray = %d
-
-

http://%s/

- pir_enable - pir_disable
- bot_enable - bot_disable - -

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

-

ftp://%s/ ... Username: esp, Password: esp

-
- -
-
- -)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, capture_interval, ms_per_frame, avg_frame_wrt, capture_interval * total_frames / 1000, - quality, framesize, repeat, xspeed, gray, localip, localip, 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(); - //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 - }; - - - 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); - } - - Serial.println("Camera http started"); -} +#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 89 - Jul 13, 2020 + - bot/pir enable/diable on web + - less re-progs of camera + - store settings in eprom, so it reboots back to where it was + - more pictures before movie starts to stablize exposure + + Version 86 - Jun 30, 2020 + - redo camera scheduler to reduce frame skips with slight delays between frames + - move more processing to separate priority tasks, and remove from idle loop() + - most tasks suspened waiting for events, rather than loopong checking for events, ... except ftp which still loops wating for ftp requests + - added a sd card snapshot jpg at beginning of every movie + - added a telegram.org message with opening picture and info about diskspace and rssi to follow activity on camera on your computer or phone + - added deepsleep feature to wake on PIR, and then deepsleep after movie is recorded + - added touch sensor on pin12 to enable/disable the pir sensor + - added more careful setup of difficult pins 12, 13, and 4 - used for SD and re-used for PIR, Touch, and Blinding Disk-Active Light + - added brownout handler to close files on brownout, which didn't work, but at least I can deepsleep to prevent multiple brownout reboots + - inside a brownout handler, you have only 300ms and you cannot access wifi, sd, or flash, ... so cannot close files, or send message + - re-used pin 4 Blinding Disk-Active Light to blink gently at beginning of movie, and at a Touch - ironically, also turns on during Brownout ;-) + - added several functions to enable / disable pir or bot using internet + http://desklens.local/bot_enable + http://desklens.local/bot_disable + http://desklens.local/pir_enable + http://desklens.local/pir_disable + - moved many settings to a separate file "settings.h" so you edit that, rather than digging through the main file to set your wifi password, startup defaults, + and enable/disable internet, pir, telegram, etc + - not super-elegant code ... still haven't written the avi writer into a nice library + - read comment on rtc_cntl.h below which may or may not be updated in the esp32 board library - links and info below + Hardware + - to use PIR function, put an active high PIR or microwave on pin 12 with a 10k resistor (brown,black,orange) to avoid antagonizing sd card + - to use Touch function, put a wire (with optional metal touch point) on pin 13 and touch it to enable/disable pir + - Blinding Disk-Active Light will give little blink during a touch, or when starting a recording + - red led on back with blink with every frame if you have that enabled in settings + +*/ + +/* +Using library ESP32 at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32 +Using library EEPROM at version 1.0.3 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\EEPROM +Using library WiFi at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi +Using library WiFiClientSecure at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFiClientSecure +Using library ArduinoJson at version 6.15.2 in folder: C:\Users\James\Documents\Arduino\libraries\ArduinoJson +Using library ESPmDNS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESPmDNS +Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC +Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS +Using library HTTPClient at version 1.2 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\HTTPClient +*/ + + +static const char vernum[] = "v89"; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// 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" + +#include +#include +#include "UniversalTelegramBot.h" + +WiFiClientSecure client; + +UniversalTelegramBot bot(BOTtoken, client); + +int diskspeed = 0; +char fname[100]; +int send_a_telegram = 0; +int Wait_for_bot = 0; + +#include + +#include "ESP32FtpServer.h" +#include + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + +// 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 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 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, 0x38, 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); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); + + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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(); + 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 x[1]; + + 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" + +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); + + touchAttachInterrupt(T5, get_touch5, 30); + Serial.print("Touch T5 = "); + for (int i = 0; i < 5; i++) { + Serial.print( touchRead(T5) ); Serial.print(", "); + } + Serial.println(" "); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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"); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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]; + + if (framesize == 6) { + 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 == 7) { + 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 == 10) { + 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 == 5) { + 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); + ///// + + ///// + 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("<"); + } + + } + //esp_camera_fb_return(fb); + ///// + esp_camera_fb_return(fb); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// send_photo_telegram - send an opening frame to telegram to see on phone or computer +// +// - this often fails if run at same time as a recording due to heap +// - the telegram ssl connection needs about 60k, the the sd write fuctions needs heap as well +// + +static esp_err_t send_photo_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, 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("<"); + } + + + esp_camera_fb_return(fb); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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 +// +// - turns on Blinding Disk-Active light and deepsleeps at the end +// - not the correct action if you are using a non-protected lipo battery, but does +// prevent multiple reboots on a weak battery, and alets the human with the bright led +// +// - mostly included as information, as it was a lot of work and didn't ultimately work to close +// the avi files + +void IRAM_ATTR low_voltage_save(void *arg) { + Serial.print("\nJZ low voltage handler\nStarting at "); + long start_of_inter = millis(); + Serial.println(start_of_inter); + time(&now); + const char *strdate = ctime(&now); + Serial.println(strdate); + + //recording = 0; + //Serial.println("\nstopping recording"); + + Serial.print("low volt, 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("\nBrown 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); + + for (int i = 0; i < 1000000; i++) { + Serial.print(millis() - start_of_inter); Serial.print(" ms, i = "); Serial.println(i); + 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 + + esp_deep_sleep_start(); + } + } + + Serial.println("... switching to system shutdown ..."); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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; + } +} + +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 == 271) { + 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 = 271; + 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" + +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("-------------------------------------"); + + ++bootCount; + Serial.println("Boot number: " + String(bootCount)); + print_wakeup_reason(); + + 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) startCameraServer(); + + // zzz username and password for ftp server + + //plm print_ram(); delay(2000); + Serial.println("Starting ftp ..."); + + if (Internet_Enabled) ftpSrv.begin("esp", "esp"); + + 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); + + + if (Internet_Enabled) { + xTaskCreatePinnedToCore( + codeForFtpTask, + "FtpTask", + 4096, // heap + NULL, + 4, // prio higher than 1 + &FtpTask, + 0); // on cpu 0 + + delay(20); + } + //plm print_ram(); delay(2000); + Serial.println("Starting camera ..."); + + recording = 0; // we are NOT recording + config_camera(); + + setupinterrupts(); + + 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); + //plm print_ram(); delay(2000); +} + + +// +// 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("\nBrown regsiter was (in hex)"); Serial.println(brown_reg_temp, HEX); + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector + + 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 (!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 + 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( ) { + + 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; + } + } + } + } + + // 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(" "); + 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; + + 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(2); + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); + + if (framesize == 6) { + 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 == 7) { + 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 == 10) { + 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 == 5) { + 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); + + if (framesize == 6) { + + 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 == 10) { + + 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 == 7) { + + 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 == 5) { + + 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; + + 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); + } + + 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); + + 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); + + 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 / 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; + } + 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); + 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(); + } + } + } + count_loop++; + wakeup = millis(); + if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes + last_wakeup = millis(); + do_time(); + + //plm print_ram(); + } + + 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; + } + } + + 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); + } + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 10; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 7; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 6; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 5; + } else { + Serial.println("Only UXGA, 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 >= 10 && x <= 50) { // MINIMUM QUALITY 10 to save memory + new_quality = x; + } + + 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 => gray=%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 => gray=%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); + + 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d
+ PIR = %d
+ BOT = %d

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

%s
ESP32-CAM Video Recorder %s


+

%s


+

http://%s/

+
http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0&pir=1&bot=1 +
VGA 2 fps, for 30 minutes repeat, 15x playback +
UXGA 1 sec per frame, for 30 minutes repeat, 30x playback, with bot +
UXGA 2 fps for 30 minutes repeat, 15x playback +
UXGA 10 sec per frame for 1 hour x300 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
+ 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d, Playback Speed = %d, Gray = %d
+
+

http://%s/

+ pir_enable + pir_disable
+ bot_enable + bot_disable + +

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

+

ftp://%s/ ... Username: esp, Password: esp

+
+ +
+
+ +)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, capture_interval, ms_per_frame, avg_frame_wrt, capture_interval * total_frames / 1000, + quality, framesize, repeat, xspeed, gray, localip, localip, 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(); + //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 + }; + + + 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); + } + + Serial.println("Camera http started"); +} diff --git a/v89/UniversalTelegramBot.cpp b/old/v89/UniversalTelegramBot.cpp similarity index 96% rename from v89/UniversalTelegramBot.cpp rename to old/v89/UniversalTelegramBot.cpp index f6d92a3..2e27e34 100644 --- a/v89/UniversalTelegramBot.cpp +++ b/old/v89/UniversalTelegramBot.cpp @@ -1,952 +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(""); - 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(""); - 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(); - } -} +/* + 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(""); + 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(""); + 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/v94/UniversalTelegramBot.h b/old/v89/UniversalTelegramBot.h similarity index 97% rename from v94/UniversalTelegramBot.h rename to old/v89/UniversalTelegramBot.h index 8004005..7181b6a 100644 --- a/v94/UniversalTelegramBot.h +++ b/old/v89/UniversalTelegramBot.h @@ -1,125 +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 = 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 +/* +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 = 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/v89/readme.md b/old/v89/readme.md similarity index 100% rename from v89/readme.md rename to old/v89/readme.md diff --git a/v94/rtc_cntl.h b/old/v89/rtc_cntl.h similarity index 97% rename from v94/rtc_cntl.h rename to old/v89/rtc_cntl.h index 1fd303a..9d04c09 100644 --- a/v94/rtc_cntl.h +++ b/old/v89/rtc_cntl.h @@ -1,64 +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 +// ... 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/v89/settings.h b/old/v89/settings.h similarity index 98% rename from v89/settings.h rename to old/v89/settings.h index c9a71c0..00fdada 100644 --- a/v89/settings.h +++ b/old/v89/settings.h @@ -1,54 +1,54 @@ -static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames - -// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here -#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT - -// 1 for blink red led with every sd card write, at your frame rate -// 0 for blink only for skipping frames and SOS if camera or sd is broken -#define BlinkWithWrite 1 - -// EDIT ssid and password -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 - -// 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 = 6; // 10 UXGA, 7 SVGA, 6 VGA, 5 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 = 10; // 10 UXGA, 7 SVGA, 6 VGA, 5 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 = 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 = 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 - -RTC_DATA_ATTR int EnableBOT = 0; -#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org -#define BOTme "1234567890" +static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames + +// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here +#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT + +// 1 for blink red led with every sd card write, at your frame rate +// 0 for blink only for skipping frames and SOS if camera or sd is broken +#define BlinkWithWrite 1 + +// EDIT ssid and password +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 + +// 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 = 6; // 10 UXGA, 7 SVGA, 6 VGA, 5 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 = 10; // 10 UXGA, 7 SVGA, 6 VGA, 5 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 = 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 = 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 + +RTC_DATA_ATTR int EnableBOT = 0; +#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org +#define BOTme "1234567890" diff --git a/v86/ESP32FtpServer.cpp b/old/v94/ESP32FtpServer.cpp similarity index 96% rename from v86/ESP32FtpServer.cpp rename to old/v94/ESP32FtpServer.cpp index c9323aa..55da8eb 100644 --- a/v86/ESP32FtpServer.cpp +++ b/old/v94/ESP32FtpServer.cpp @@ -1,1173 +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; -} +/* + * 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/v89/ESP32FtpServer.h b/old/v94/ESP32FtpServer.h similarity index 97% rename from v89/ESP32FtpServer.h rename to old/v94/ESP32FtpServer.h index 23869e5..bb6d67d 100644 --- a/v89/ESP32FtpServer.h +++ b/old/v94/ESP32FtpServer.h @@ -1,116 +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 + +/* +* 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/v94/TimeLapseAvi94x.ino b/old/v94/TimeLapseAvi94x.ino similarity index 96% rename from v94/TimeLapseAvi94x.ino rename to old/v94/TimeLapseAvi94x.ino index f150782..be5bd74 100644 --- a/v94/TimeLapseAvi94x.ino +++ b/old/v94/TimeLapseAvi94x.ino @@ -1,2629 +1,2629 @@ -#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 94 - Jul 28, 2020 - - live stream stuff - http://desklens.local/stream - - Version 90 - Jul 22, 2020 - - get rid of the bad jpeg's, print out the number - - Version 89 - Jul 13, 2020 - - bot/pir enable/diable on web - - less re-progs of camera - - store settings in eprom, so it reboots back to where it was - - more pictures before movie starts to stablize exposure - - Version 86 - Jun 30, 2020 - - redo camera scheduler to reduce frame skips with slight delays between frames - - move more processing to separate priority tasks, and remove from idle loop() - - most tasks suspened waiting for events, rather than loopong checking for events, ... except ftp which still loops wating for ftp requests - - added a sd card snapshot jpg at beginning of every movie - - added a telegram.org message with opening picture and info about diskspace and rssi to follow activity on camera on your computer or phone - - added deepsleep feature to wake on PIR, and then deepsleep after movie is recorded - - added touch sensor on pin12 to enable/disable the pir sensor - - added more careful setup of difficult pins 12, 13, and 4 - used for SD and re-used for PIR, Touch, and Blinding Disk-Active Light - - added brownout handler to close files on brownout, which didn't work, but at least I can deepsleep to prevent multiple brownout reboots - - inside a brownout handler, you have only 300ms and you cannot access wifi, sd, or flash, ... so cannot close files, or send message - - re-used pin 4 Blinding Disk-Active Light to blink gently at beginning of movie, and at a Touch - ironically, also turns on during Brownout ;-) - - added several functions to enable / disable pir or bot using internet - http://desklens.local/bot_enable - http://desklens.local/bot_disable - http://desklens.local/pir_enable - http://desklens.local/pir_disable - - moved many settings to a separate file "settings.h" so you edit that, rather than digging through the main file to set your wifi password, startup defaults, - and enable/disable internet, pir, telegram, etc - - not super-elegant code ... still haven't written the avi writer into a nice library - - read comment on rtc_cntl.h below which may or may not be updated in the esp32 board library - links and info below - Hardware - - to use PIR function, put an active high PIR or microwave on pin 12 with a 10k resistor (brown,black,orange) to avoid antagonizing sd card - - to use Touch function, put a wire (with optional metal touch point) on pin 13 and touch it to enable/disable pir - - Blinding Disk-Active Light will give little blink during a touch, or when starting a recording - - red led on back with blink with every frame if you have that enabled in settings - -*/ - -/* - Using library ESP32 at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32 - Using library EEPROM at version 1.0.3 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\EEPROM - Using library WiFi at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi - Using library WiFiClientSecure at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFiClientSecure - Using library ArduinoJson at version 6.15.2 in folder: C:\Users\James\Documents\Arduino\libraries\ArduinoJson - Using library ESPmDNS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESPmDNS - Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC - Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS - Using library HTTPClient at version 1.2 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\HTTPClient -*/ - - -static const char vernum[] = "v94"; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// 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" - -#include -#include -#include "UniversalTelegramBot.h" - -WiFiClientSecure client; - -UniversalTelegramBot bot(BOTtoken, client); - -int diskspeed = 0; -char fname[100]; -int send_a_telegram = 0; -int Wait_for_bot = 0; - -#include - -#include "ESP32FtpServer.h" -#include - -FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial - -// 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 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, 0x34, 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); - } - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); - - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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 x[1]; - - 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" - -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); - - touchAttachInterrupt(T5, get_touch5, 30); - Serial.print("Touch T5 = "); - for (int i = 0; i < 5; i++) { - Serial.print( touchRead(T5) ); Serial.print(", "); - } - Serial.println(" "); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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"); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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]; - - if (framesize == 6) { - 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 == 7) { - 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 == 10) { - 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 == 5) { - 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); - ///// - - ///// - 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("<"); - } - - } - //esp_camera_fb_return(fb); - ///// - esp_camera_fb_return(fb); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// send_photo_telegram - send an opening frame to telegram to see on phone or computer -// -// - this often fails if run at same time as a recording due to heap -// - the telegram ssl connection needs about 60k, the the sd write fuctions needs heap as well -// - -static esp_err_t send_photo_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, 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("<"); - } - - - esp_camera_fb_return(fb); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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; -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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 -// -// - turns on Blinding Disk-Active light and deepsleeps at the end -// - not the correct action if you are using a non-protected lipo battery, but does -// prevent multiple reboots on a weak battery, and alets the human with the bright led -// -// - mostly included as information, as it was a lot of work and didn't ultimately work to close -// the avi files - -void IRAM_ATTR low_voltage_save(void *arg) { - Serial.print("\nJZ low voltage handler\nStarting at "); - long start_of_inter = millis(); - Serial.println(start_of_inter); - time(&now); - const char *strdate = ctime(&now); - Serial.println(strdate); - - //recording = 0; - //Serial.println("\nstopping recording"); - - Serial.print("low volt, 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); - - for (int i = 0; i < 1000000; i++) { - Serial.print(millis() - start_of_inter); Serial.print(" ms, i = "); Serial.println(i); - 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); - 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 ..."); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 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; - } -} - -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" - -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("-------------------------------------"); - - ++bootCount; - Serial.println("Boot number: " + String(bootCount)); - print_wakeup_reason(); - - 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) startCameraServer(); - - // zzz username and password for ftp server - - //plm print_ram(); delay(2000); - Serial.println("Starting ftp ..."); - - if (Internet_Enabled) ftpSrv.begin("esp", "esp"); - - 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); - - - if (Internet_Enabled) { - xTaskCreatePinnedToCore( - codeForFtpTask, - "FtpTask", - 4096, // heap - NULL, - 4, // prio higher than 1 - &FtpTask, - 0); // on cpu 0 - - delay(20); - } - //plm print_ram(); delay(2000); - Serial.println("Starting camera ..."); - - recording = 0; // we are NOT recording - config_camera(); - - setupinterrupts(); - - 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 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 - - 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 (!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 - 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( ) { - - 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; - } - } - } - } - - // 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(" "); - 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; - - 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); - - if (framesize == 6) { - 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 == 7) { - 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 == 10) { - 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 == 5) { - 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); - - if (framesize == 6) { - - 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 == 10) { - - 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 == 7) { - - 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 == 5) { - - 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); - - 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); - - 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 / total_frames, 1 ); - Serial.print("Normal jpg % "); Serial.println( 100.0 * normal_jpg / total_frames, 1 ); - Serial.print("Extend jpg % "); Serial.println( 100.0 * extend_jpg / total_frames, 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; - } - 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(); - } - } - } - count_loop++; - wakeup = millis(); - if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes - last_wakeup = millis(); - do_time(); - - //plm print_ram(); - } - - 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; - } - } - - 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); - } - if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { - if (strcmp(new_res, "UXGA") == 0) { - new_framesize = 10; - } else if (strcmp(new_res, "SVGA") == 0) { - new_framesize = 7; - } else if (strcmp(new_res, "VGA") == 0) { - new_framesize = 6; - } else if (strcmp(new_res, "CIF") == 0) { - new_framesize = 5; - } else { - Serial.println("Only UXGA, 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 => gray=%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 => gray=%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); - - 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d
- Speed = %d
- Gray = %d
- PIR = %d
- BOT = %d

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

%s
ESP32-CAM Video Recorder %s


-

%s


-

http://%s/

-
http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0&pir=1&bot=1 -
VGA 2 fps, for 30 minutes repeat, 15x playback -
UXGA 1 fps, for 30 minutes repeat, 30x playback, with bot -
UXGA 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
- Repeat = %d, Playback Speed = %d, Gray = %d
-
-

http://%s/

-

Stream at %d ms

- pir_enable   - pir_disable
- bot_enable   - bot_disable - -

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

-

ftp://%s/ ... Username: esp, Password: esp

-
- -
-
- -)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 - }; - - httpd_uri_t stream_uri = { - .uri = "/stream", - .method = HTTP_GET, - .handler = stream_handler, - .user_ctx = NULL - }; - - 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); - - httpd_register_uri_handler(camera_httpd, &stream_uri); - - - } - - Serial.println("Camera http started"); -} +#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 94 - Jul 28, 2020 + - live stream stuff + http://desklens.local/stream + + Version 90 - Jul 22, 2020 + - get rid of the bad jpeg's, print out the number + + Version 89 - Jul 13, 2020 + - bot/pir enable/diable on web + - less re-progs of camera + - store settings in eprom, so it reboots back to where it was + - more pictures before movie starts to stablize exposure + + Version 86 - Jun 30, 2020 + - redo camera scheduler to reduce frame skips with slight delays between frames + - move more processing to separate priority tasks, and remove from idle loop() + - most tasks suspened waiting for events, rather than loopong checking for events, ... except ftp which still loops wating for ftp requests + - added a sd card snapshot jpg at beginning of every movie + - added a telegram.org message with opening picture and info about diskspace and rssi to follow activity on camera on your computer or phone + - added deepsleep feature to wake on PIR, and then deepsleep after movie is recorded + - added touch sensor on pin12 to enable/disable the pir sensor + - added more careful setup of difficult pins 12, 13, and 4 - used for SD and re-used for PIR, Touch, and Blinding Disk-Active Light + - added brownout handler to close files on brownout, which didn't work, but at least I can deepsleep to prevent multiple brownout reboots + - inside a brownout handler, you have only 300ms and you cannot access wifi, sd, or flash, ... so cannot close files, or send message + - re-used pin 4 Blinding Disk-Active Light to blink gently at beginning of movie, and at a Touch - ironically, also turns on during Brownout ;-) + - added several functions to enable / disable pir or bot using internet + http://desklens.local/bot_enable + http://desklens.local/bot_disable + http://desklens.local/pir_enable + http://desklens.local/pir_disable + - moved many settings to a separate file "settings.h" so you edit that, rather than digging through the main file to set your wifi password, startup defaults, + and enable/disable internet, pir, telegram, etc + - not super-elegant code ... still haven't written the avi writer into a nice library + - read comment on rtc_cntl.h below which may or may not be updated in the esp32 board library - links and info below + Hardware + - to use PIR function, put an active high PIR or microwave on pin 12 with a 10k resistor (brown,black,orange) to avoid antagonizing sd card + - to use Touch function, put a wire (with optional metal touch point) on pin 13 and touch it to enable/disable pir + - Blinding Disk-Active Light will give little blink during a touch, or when starting a recording + - red led on back with blink with every frame if you have that enabled in settings + +*/ + +/* + Using library ESP32 at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESP32 + Using library EEPROM at version 1.0.3 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\EEPROM + Using library WiFi at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFi + Using library WiFiClientSecure at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\WiFiClientSecure + Using library ArduinoJson at version 6.15.2 in folder: C:\Users\James\Documents\Arduino\libraries\ArduinoJson + Using library ESPmDNS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\ESPmDNS + Using library SD_MMC at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SD_MMC + Using library FS at version 1.0 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\FS + Using library HTTPClient at version 1.2 in folder: C:\Users\James\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\HTTPClient +*/ + + +static const char vernum[] = "v94"; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// 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" + +#include +#include +#include "UniversalTelegramBot.h" + +WiFiClientSecure client; + +UniversalTelegramBot bot(BOTtoken, client); + +int diskspeed = 0; +char fname[100]; +int send_a_telegram = 0; +int Wait_for_bot = 0; + +#include + +#include "ESP32FtpServer.h" +#include + +FtpServer ftpSrv; //set #define FTP_DEBUG in ESP32FtpServer.h to see ftp verbose on serial + +// 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 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, 0x34, 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); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); + + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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 x[1]; + + 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" + +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); + + touchAttachInterrupt(T5, get_touch5, 30); + Serial.print("Touch T5 = "); + for (int i = 0; i < 5; i++) { + Serial.print( touchRead(T5) ); Serial.print(", "); + } + Serial.println(" "); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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"); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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]; + + if (framesize == 6) { + 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 == 7) { + 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 == 10) { + 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 == 5) { + 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); + ///// + + ///// + 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("<"); + } + + } + //esp_camera_fb_return(fb); + ///// + esp_camera_fb_return(fb); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// send_photo_telegram - send an opening frame to telegram to see on phone or computer +// +// - this often fails if run at same time as a recording due to heap +// - the telegram ssl connection needs about 60k, the the sd write fuctions needs heap as well +// + +static esp_err_t send_photo_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, 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("<"); + } + + + esp_camera_fb_return(fb); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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; +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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 +// +// - turns on Blinding Disk-Active light and deepsleeps at the end +// - not the correct action if you are using a non-protected lipo battery, but does +// prevent multiple reboots on a weak battery, and alets the human with the bright led +// +// - mostly included as information, as it was a lot of work and didn't ultimately work to close +// the avi files + +void IRAM_ATTR low_voltage_save(void *arg) { + Serial.print("\nJZ low voltage handler\nStarting at "); + long start_of_inter = millis(); + Serial.println(start_of_inter); + time(&now); + const char *strdate = ctime(&now); + Serial.println(strdate); + + //recording = 0; + //Serial.println("\nstopping recording"); + + Serial.print("low volt, 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); + + for (int i = 0; i < 1000000; i++) { + Serial.print(millis() - start_of_inter); Serial.print(" ms, i = "); Serial.println(i); + 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); + 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 ..."); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// 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; + } +} + +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" + +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("-------------------------------------"); + + ++bootCount; + Serial.println("Boot number: " + String(bootCount)); + print_wakeup_reason(); + + 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) startCameraServer(); + + // zzz username and password for ftp server + + //plm print_ram(); delay(2000); + Serial.println("Starting ftp ..."); + + if (Internet_Enabled) ftpSrv.begin("esp", "esp"); + + 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); + + + if (Internet_Enabled) { + xTaskCreatePinnedToCore( + codeForFtpTask, + "FtpTask", + 4096, // heap + NULL, + 4, // prio higher than 1 + &FtpTask, + 0); // on cpu 0 + + delay(20); + } + //plm print_ram(); delay(2000); + Serial.println("Starting camera ..."); + + recording = 0; // we are NOT recording + config_camera(); + + setupinterrupts(); + + 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 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 + + 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 (!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 + 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( ) { + + 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; + } + } + } + } + + // 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(" "); + 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; + + 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); + + if (framesize == 6) { + 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 == 7) { + 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 == 10) { + 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 == 5) { + 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); + + if (framesize == 6) { + + 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 == 10) { + + 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 == 7) { + + 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 == 5) { + + 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); + + 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); + + 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 / total_frames, 1 ); + Serial.print("Normal jpg % "); Serial.println( 100.0 * normal_jpg / total_frames, 1 ); + Serial.print("Extend jpg % "); Serial.println( 100.0 * extend_jpg / total_frames, 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; + } + 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(); + } + } + } + count_loop++; + wakeup = millis(); + if (wakeup - last_wakeup > (13 * 60 * 1000) ) { // 13 minutes + last_wakeup = millis(); + do_time(); + + //plm print_ram(); + } + + 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; + } + } + + 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); + } + if (httpd_query_key_value(buf, "framesize", new_res, sizeof(new_res)) == ESP_OK) { + if (strcmp(new_res, "UXGA") == 0) { + new_framesize = 10; + } else if (strcmp(new_res, "SVGA") == 0) { + new_framesize = 7; + } else if (strcmp(new_res, "VGA") == 0) { + new_framesize = 6; + } else if (strcmp(new_res, "CIF") == 0) { + new_framesize = 5; + } else { + Serial.println("Only UXGA, 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 => gray=%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 => gray=%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); + + 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d
+ Speed = %d
+ Gray = %d
+ PIR = %d
+ BOT = %d

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

%s
ESP32-CAM Video Recorder %s


+

%s


+

http://%s/

+
http://%s/start?framesize=VGA&length=1800&interval=100&quality=10&repeat=100&speed=1&gray=0&pir=1&bot=1 +
VGA 2 fps, for 30 minutes repeat, 15x playback +
UXGA 1 fps, for 30 minutes repeat, 30x playback, with bot +
UXGA 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 (10 UXGA, 7 SVGA, 6 VGA, 5 CIF)
+ Repeat = %d, Playback Speed = %d, Gray = %d
+
+

http://%s/

+

Stream at %d ms

+ pir_enable   + pir_disable
+ bot_enable   + bot_disable + +

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

+

ftp://%s/ ... Username: esp, Password: esp

+
+ +
+
+ +)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 + }; + + httpd_uri_t stream_uri = { + .uri = "/stream", + .method = HTTP_GET, + .handler = stream_handler, + .user_ctx = NULL + }; + + 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); + + httpd_register_uri_handler(camera_httpd, &stream_uri); + + + } + + Serial.println("Camera http started"); +} diff --git a/v94/UniversalTelegramBot.cpp b/old/v94/UniversalTelegramBot.cpp similarity index 96% rename from v94/UniversalTelegramBot.cpp rename to old/v94/UniversalTelegramBot.cpp index f6d92a3..2e27e34 100644 --- a/v94/UniversalTelegramBot.cpp +++ b/old/v94/UniversalTelegramBot.cpp @@ -1,952 +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(""); - 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(""); - 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(); - } -} +/* + 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(""); + 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(""); + 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/v89/UniversalTelegramBot.h b/old/v94/UniversalTelegramBot.h similarity index 97% rename from v89/UniversalTelegramBot.h rename to old/v94/UniversalTelegramBot.h index 8004005..7181b6a 100644 --- a/v89/UniversalTelegramBot.h +++ b/old/v94/UniversalTelegramBot.h @@ -1,125 +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 = 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 +/* +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 = 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/v94/readme b/old/v94/readme similarity index 100% rename from v94/readme rename to old/v94/readme diff --git a/v86/rtc_cntl.h b/old/v94/rtc_cntl.h similarity index 97% rename from v86/rtc_cntl.h rename to old/v94/rtc_cntl.h index 1fd303a..9d04c09 100644 --- a/v86/rtc_cntl.h +++ b/old/v94/rtc_cntl.h @@ -1,64 +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 +// ... 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/v94/settings.h b/old/v94/settings.h similarity index 98% rename from v94/settings.h rename to old/v94/settings.h index f917a27..ac4d3f2 100644 --- a/v94/settings.h +++ b/old/v94/settings.h @@ -1,57 +1,57 @@ -static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames - -// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here -#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT - -// 1 for blink red led with every sd card write, at your frame rate -// 0 for blink only for skipping frames and SOS if camera or sd is broken -#define BlinkWithWrite 1 - -// EDIT ssid and password -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 = 314; // 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 = 6; // 10 UXGA, 7 SVGA, 6 VGA, 5 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 = 10; // 10 UXGA, 7 SVGA, 6 VGA, 5 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; // change to 1 for enable - must fill in your numbers below -#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org -#define BOTme "1234567890" +static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames + +// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here +#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT + +// 1 for blink red led with every sd card write, at your frame rate +// 0 for blink only for skipping frames and SOS if camera or sd is broken +#define BlinkWithWrite 1 + +// EDIT ssid and password +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 = 314; // 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 = 6; // 10 UXGA, 7 SVGA, 6 VGA, 5 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 = 10; // 10 UXGA, 7 SVGA, 6 VGA, 5 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; // change to 1 for enable - must fill in your numbers below +#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org +#define BOTme "1234567890" diff --git a/v94/web_v94.jpg b/old/v94/web_v94.jpg similarity index 100% rename from v94/web_v94.jpg rename to old/v94/web_v94.jpg