Jan 12, 2022 - version A1

- update for esp32-arduino 2.02
- replace ftp file transfer with http file transfer directly from browser
- obscure features deleted
master
jameszah 2022-01-12 23:16:28 -07:00 zatwierdzone przez GitHub
rodzic 7fd2544bfc
commit 1d928bb214
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
10 zmienionych plików z 5683 dodań i 0 usunięć

59
vA1/CRC32.cpp 100644
Wyświetl plik

@ -0,0 +1,59 @@
// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr
// based on https://github.com/holgerlembke/ESPxWebFlMgr
//
// Copyright (c) 2013 Christopher Baker <https://christopherbaker.net>
//
// SPDX-License-Identifier: MIT
//
#include "CRC32.h"
// Conditionally use pgm memory if it is available.
#if defined(PROGMEM)
#define FLASH_PROGMEM PROGMEM
#define FLASH_READ_DWORD(x) (pgm_read_dword_near(x))
#else
#define FLASH_PROGMEM
#define FLASH_READ_DWORD(x) (*(uint32_t*)(x))
#endif
static const uint32_t crc32_table[] FLASH_PROGMEM = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
CRC32::CRC32()
{
reset();
}
void CRC32::reset()
{
_state = ~0L;
}
void CRC32::update(const uint8_t& data)
{
// via http://forum.arduino.cc/index.php?topic=91179.0
uint8_t tbl_idx = 0;
tbl_idx = _state ^ (data >> (0 * 4));
_state = FLASH_READ_DWORD(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4);
tbl_idx = _state ^ (data >> (1 * 4));
_state = FLASH_READ_DWORD(crc32_table + (tbl_idx & 0x0f)) ^ (_state >> 4);
}
uint32_t CRC32::finalize() const
{
return ~_state;
}

77
vA1/CRC32.h 100644
Wyświetl plik

@ -0,0 +1,77 @@
// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr
// based on https://github.com/holgerlembke/ESPxWebFlMgr
//
// Copyright (c) 2013 Christopher Baker <https://christopherbaker.net>
//
// SPDX-License-Identifier: MIT
//
#pragma once
#include "Arduino.h"
/// \brief A class for calculating the CRC32 checksum from arbitrary data.
/// \sa http://forum.arduino.cc/index.php?topic=91179.0
class CRC32
{
public:
/// \brief Initialize an empty CRC32 checksum.
CRC32();
/// \brief Reset the checksum claculation.
void reset();
/// \brief Update the current checksum caclulation with the given data.
/// \param data The data to add to the checksum.
void update(const uint8_t& data);
/// \brief Update the current checksum caclulation with the given data.
/// \tparam Type The data type to read.
/// \param data The data to add to the checksum.
template <typename Type>
void update(const Type& data)
{
update(&data, 1);
}
/// \brief Update the current checksum caclulation with the given data.
/// \tparam Type The data type to read.
/// \param data The array to add to the checksum.
/// \param size Size of the array to add.
template <typename Type>
void update(const Type* data, size_t size)
{
size_t nBytes = size * sizeof(Type);
const uint8_t* pData = (const uint8_t*)data;
for (size_t i = 0; i < nBytes; i++)
{
update(pData[i]);
}
}
/// \returns the caclulated checksum.
uint32_t finalize() const;
/// \brief Calculate the checksum of an arbitrary data array.
/// \tparam Type The data type to read.
/// \param data A pointer to the data to add to the checksum.
/// \param size The size of the data to add to the checksum.
/// \returns the calculated checksum.
template <typename Type>
static uint32_t calculate(const Type* data, size_t size)
{
CRC32 crc;
crc.update(data, size);
return crc.finalize();
}
private:
/// \brief The internal checksum state.
uint32_t _state = ~0L;
};

Wyświetl plik

@ -0,0 +1,925 @@
// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr
// Jan 12, 2022 - adds dates/times to display
// based on https://github.com/holgerlembke/ESPxWebFlMgr
#include <Arduino.h>
#include <inttypes.h>
#include "ESPxWebFlMgr.h"
#include "ESPxWebFlMgrWp.h"
#include "ESPxWebFlMgrWpF.h"
#include "crc32.h"
#include <time.h> //jz
#ifdef ESP8266
#include <ESP8266WebServer.h>
#include <FS.h>
#endif
#ifdef ESP32
#include <WebServer.h>
#include <FS.h>
#include <SD_MMC.h> //jz #include <SPIFFS.h>
#include <detail/RequestHandlersImpl.h>
#endif
String getContentType(const String& path) {
#ifdef ESP8266
return mime::getContentType(path);
#endif
#ifdef ESP32
return StaticRequestHandler::getContentType(path);
#endif
}
//*****************************************************************************************************
ESPxWebFlMgr::ESPxWebFlMgr(word port) {
_Port = port;
}
//*****************************************************************************************************
ESPxWebFlMgr::~ESPxWebFlMgr() {
end();
}
//*****************************************************************************************************
void ESPxWebFlMgr::begin() {
#ifdef ESP8266
fileManager = new ESP8266WebServer(_Port);
#endif
#ifdef ESP32
fileManager = new WebServer(_Port);
#endif
#ifdef fileManagerServerStaticsInternally
fileManager->on("/", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerIndexpage, this));
fileManager->on("/fm.css", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerCSS, this));
fileManager->on("/fm.js", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerJS, this));
#endif
fileManager->on("/bg.css", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerGetBackGround, this));
fileManager->on("/i", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerFileListInsert, this));
fileManager->on("/c", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerCommandExecutor, this));
fileManager->on("/e", HTTP_GET, std::bind(&ESPxWebFlMgr::fileManagerFileEditorInsert, this));
// file receiver with attached file to form
fileManager->on("/r", HTTP_POST, std::bind(&ESPxWebFlMgr::fileManagerReceiverOK, this),
std::bind(&ESPxWebFlMgr::fileManagerReceiver, this));
fileManager->onNotFound(std::bind(&ESPxWebFlMgr::fileManagerNotFound, this));
fileManager->begin();
}
//*****************************************************************************************************
void ESPxWebFlMgr::end() {
if (fileManager) {
delete fileManager;
fileManager = NULL;
}
}
//*****************************************************************************************************
void ESPxWebFlMgr::handleClient() {
if (fileManager) {
fileManager->handleClient();
}
}
//*****************************************************************************************************
void ESPxWebFlMgr::setViewSysFiles(bool vsf) {
_ViewSysFiles = vsf;
}
//*****************************************************************************************************
bool ESPxWebFlMgr::getViewSysFiles(void) {
return _ViewSysFiles;
}
//*****************************************************************************************************
void ESPxWebFlMgr::setSysFileStartPattern(String sfsp) {
_SysFileStartPattern = sfsp;
}
//*****************************************************************************************************
String ESPxWebFlMgr::getSysFileStartPattern(void) {
return _SysFileStartPattern;
}
//*****************************************************************************************************
// privates start here
//*****************************************************************************************************
//*****************************************************************************************************
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerGetBackGround(void) {
fileManager->send(200, F("text/css"), ".background {background-color: " + _backgroundColor + ";}");
}
//*****************************************************************************************************
void ESPxWebFlMgr::setBackGroundColor(const String backgroundColor) {
_backgroundColor = backgroundColor;
}
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerNotFound(void) {
String uri = fileManager->uri();
if (uri == "/") {
uri = "/fm.html";
}
String contentTyp = getContentType(uri);
if (ESPxWebFlMgr_FileSystem.exists(uri)) {
File f = ESPxWebFlMgr_FileSystem.open(uri, "r");
if (f) {
if (fileManager->streamFile(f, contentTyp) != f.size()) {
// Serial.println(F("Sent less data than expected!"));
// We should panic a little bit.
}
f.close();
}
} else
{
fileManager->send(404, F("text/plain"), F("URI not found."));
}
}
//*****************************************************************************************************
String ESPxWebFlMgr::dispIntDotted(size_t i) {
String res = "";
while (i != 0) {
int r = i % 1000;
res = String(i % 1000) + res;
i /= 1000;
if ( (r < 100) && (i > 0) ) {
res = "0" + res;
if (r < 10) {
res = "0" + res;
}
}
if (i != 0) {
res = "," + res; //jz dot to comma ;-)
}
}
return res;
}
//*****************************************************************************************************
size_t ESPxWebFlMgr::totalBytes(void) {
#ifdef ESP8266
FSInfo info;
ESPxWebFlMgr_FileSystem.info(info);
return info.totalBytes;
#endif
#ifdef ESP32
return (ESPxWebFlMgr_FileSystem.totalBytes() / 1024);
#endif
}
//*****************************************************************************************************
size_t ESPxWebFlMgr::usedBytes(void) {
#ifdef ESP8266
FSInfo info;
ESPxWebFlMgr_FileSystem.info(info);
return info.usedBytes;
#endif
#ifdef ESP32
return (ESPxWebFlMgr_FileSystem.usedBytes() / 1024);
#endif
}
//*****************************************************************************************************
String ESPxWebFlMgr::dispFileString(size_t fs) {
if (fs < 0) {
return "-0";
}
if (fs == 0) {
return "0 kB";
}
if (fs < 1000) {
return String(fs) + " kB";
}
// switch from bytes to kilobytes due to 4gb+ sd cards //jz
//String units[] = { "B", "kB", "MB", "GB", "TB" };
String units[] = { "kB", "MB", "GB", "TB" };
int digitGroups = (int) (log10(fs) / log10(1024));
//return String(fs / pow(1024, digitGroups)) + " " + units[digitGroups] + " <small>(" + dispIntDotted(fs) + " kB)</small>";
return String(fs / pow(1024, digitGroups)) + " " + units[digitGroups] ;
}
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerIndexpage(void) {
fileManager->send(200, F("text/html"), FPSTR(ESPxWebFlMgrWpindexpage));
delay(1);
}
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerJS(void) {
fileManager->send(200, F("text/javascript"), FPSTR(ESPxWebFlMgrWpjavascript));
delay(1);
}
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerCSS(void) {
fileManager->send(200, F("text/css"), FPSTR(ESPxWebFlMgrWpcss));
delay(1);
}
//*****************************************************************************************************
String ESPxWebFlMgr::CheckFileNameLengthLimit(String fn) {
// SPIFFS file name limit. Is there a way to get the max length from SPIFFS/LittleFS?
// SPIFFS_OBJ_NAME_LEN is spiffs.... but not very clean.
if (fn.length() > 32) {
int len = fn.length();
fn.remove(29);
fn += String(len);
}
return fn;
}
//*****************************************************************************************************
String ESPxWebFlMgr::colorline(int i) {
if (i % 2 == 0) {
return "ccu";
} else {
return "ccg";
}
}
//*****************************************************************************************************
boolean ESPxWebFlMgr::allowAccessToThisFile(const String filename) {
return ! filename.startsWith(_SysFileStartPattern);
}
//*****************************************************************************************************
// jz kludge for switching folders on the sd card
String subdir = "/";
//*****************************************************************************************************
//[make FS from esp8266 and esp32 compatible]**********************************************************
// this is the way MS DOS 3.x (?) did it with Int21 findfirst/findnext/findclose
#ifdef ESP8266
File ESPxWebFlMgr::nextFile(Dir &dir) {
dir.next();
return dir.openFile("r");
}
File ESPxWebFlMgr::firstFile(Dir &dir) {
dir = ESPxWebFlMgr_FileSystem.openDir("/");
return nextFile(dir);
}
#endif
#ifdef ESP32
#define Dir File
File ESPxWebFlMgr::nextFile(Dir &dir) {
return dir.openNextFile();
}
File ESPxWebFlMgr::firstFile(Dir &dir) {
dir = ESPxWebFlMgr_FileSystem.open(subdir, "r"); //jz dir = ESPxWebFlMgr_FileSystem.open("/", "r");
return nextFile(dir);
}
#endif
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerFileListInsert(void) { // must get arg with /i to list that folder
fileManager->setContentLength(CONTENT_LENGTH_UNKNOWN);
fileManager->send(200, F("text/html"), String());
fileManager->sendContent(F("<div class=\"cc\"><div class=\"gc\">"));
bool gzipperexists = ( (ESPxWebFlMgr_FileSystem.exists("/gzipper.js.gz")) ||
(ESPxWebFlMgr_FileSystem.exists("/gzipper.js")) );
//Serial.println(fileManager->args());
//Serial.println(fileManager->argName(0));
//Serial.println(fileManager->arg(0));
if ( (fileManager->args() == 1) && (fileManager->argName(0) == "subdir") ) {
subdir = fileManager->arg(0);
//Serial.print("Arg: "); Serial.println(fileManager->arg(0));
/*
if (fileManager->arg(0) == "/"){
subdir = "/";
} else if (subdir == "/") {
subdir = fileManager->arg(0);
} else {
subdir = fileManager->arg(0);
}
Serial.print("New subdir: "); Serial.println(subdir);
*/
} else {
subdir = "/";
}
//Serial.print("Final subdir: "); Serial.println(subdir);
// first file is "go to root"
String fcd;
String direct = "ccd"; //jz bland color for directory
String fn = "/";
fcd = "<div "
"class=\"ccl " + direct + "\""
"onclick=\"opendirectory('" + fn + "')\""
">&nbsp;&nbsp;" + fn + " - GOTO ROOT DIR -" + "</div>";
fcd += "<div class=\"ccz " + direct + "\">&nbsp;" + " " + "&nbsp;</div>";
fcd += "<div class=\"cct " + direct + "\">&nbsp;" + dispIntDotted(0) + "&nbsp;</div>";
fcd += "<div class=\"ccr " + direct + "\">&nbsp;";
fcd += "&nbsp;&nbsp;</div>";
fileManager->sendContent(fcd);
// List files
int i = 0;
Dir dir;
File file = firstFile(dir);
while (file) {
String fn = file.name();
/*
Serial.print("FN: >");
Serial.print(fn);
Serial.print("<");
Serial.println();
*/
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
/*
String fc = "<div id=\"file"+String(i)+"\" "
"data-filename=\""+fn+"\""
"class=\"ccl " + colorline(i) + "\""
"onclick=\"downloadfile('" + fn + "')\""
">&nbsp;&nbsp;" + fn + "</div>";
*/
String fc;
String nsd;
if (subdir == "/") {
nsd = "/";
} else {
nsd = subdir + "/";
}
if (file.isDirectory()) {
String direct = "ccd"; //jz bland color for directory
fc = "<div "
"class=\"ccl " + direct + "\""
"onclick=\"opendirectory('" + nsd + fn + "')\""
">&nbsp;&nbsp;" + fn + " - DIR -" + "</div>";
} else {
//Serial.println(subdir);
//Serial.println(fn);
fc = "<div "
"class=\"ccl " + colorline(i) + "\""
"onclick=\"downloadfile('" + nsd + fn + "')\""
">&nbsp;&nbsp;" + fn + "</div>";
}
time_t t= file.getLastWrite();
struct tm * tmstruct = localtime(&t);
//Serial.printf(" LAST WRITE: %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec);
char ccz[100];
sprintf(ccz, " %d-%02d-%02d %02d:%02d:%02d\n",(tmstruct->tm_year)+1900,( tmstruct->tm_mon)+1, tmstruct->tm_mday,tmstruct->tm_hour , tmstruct->tm_min, tmstruct->tm_sec);
fc += "<div class=\"ccz " + colorline(i) + "\">&nbsp;" + String(ccz) + "&nbsp;</div>"; //jz
// File f = dir.openFile("r");
fc += "<div class=\"cct " + colorline(i) + "\">&nbsp;" + dispIntDotted(file.size()) + "&nbsp;</div>";
fc += "<div class=\"ccr " + colorline(i) + "\">&nbsp;"
"<button title=\"Delete\" onclick=\"deletefile('" + fn + "')\" class=\"b\">D</button> "
"<button title=\"Rename\" onclick=\"renamefile('" + fn + "')\" class=\"b\">R</button> ";
// no gziped version and (zipper or gziped zipper) exists
if ( (! (fn.endsWith(".gz")) ) && gzipperexists) {
fc += "<button title=\"Compress\" onclick=\"compressurlfile('" + fn + "')\" class=\"b\">C</button> ";
}
// for editor
#ifndef fileManagerEditEverything
String contentTyp = getContentType(fn);
if ( (contentTyp.startsWith("text/")) ||
(contentTyp.startsWith("application/j")) ) // boldly assume: json, javascript and everything else is edible....
#endif
{
fc += "<button title=\"Edit\" onclick=\"editfile('" + fn + "')\" class=\"b\">E</button> ";
}
fc += "&nbsp;&nbsp;</div>";
fileManager->sendContent(fc);
i++;
}
file = nextFile(dir);
}
// fileManager->sendContent("<span id=\"filecount\" data-count=\""+String(i)+"\"></span>");
String sinfo = "&nbsp; Size: " +
dispFileString(totalBytes()) +
", used: " +
dispFileString(usedBytes());
/*
fileManager->sendContent(F(" FS blocksize: "));
fileManager->sendContent(String(info.blockSize));
fileManager->sendContent(F(", pageSize: "));
fileManager->sendContent(String(info.pageSize));
*/
fileManager->sendContent(F("</div></div>"));
fileManager->sendContent(F("##"));
fileManager->sendContent(sinfo);
fileManager->sendContent("");
delay(1);
}
//*****************************************************************************************************
String ESPxWebFlMgr::escapeHTMLcontent(String html) {
//html.replace("<","&lt;");
//html.replace(">","&gt;");
html.replace("&", "&amp;");
return html;
}
// in place editor
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerFileEditorInsert(void) {
//Serial.println("Edit");
if ( (fileManager->args() == 1) && (fileManager->argName(0) == "edit") ) {
String fn = "/" + fileManager->arg(0);
if ( (! _ViewSysFiles) && (!allowAccessToThisFile(fn)) ) {
fileManager->send(404, F("text/plain"), F("Illegal."));
return;
}
fileManager->setContentLength(CONTENT_LENGTH_UNKNOWN);
fileManager->send(200, F("text/html"), String());
fileManager->sendContent(ESPxWebFlMgrWpFormIntro);
if (ESPxWebFlMgr_FileSystem.exists(subdir + "/" + fn)) {
File f = ESPxWebFlMgr_FileSystem.open(subdir + "/" + fn, "r");
if (f) {
do {
String l = f.readStringUntil('\n') + '\n';
l = escapeHTMLcontent(l);
fileManager->sendContent(l);
} while (f.available());
f.close();
}
}
fileManager->sendContent(ESPxWebFlMgrWpFormExtro1);
fileManager->sendContent(fn);
fileManager->sendContent(ESPxWebFlMgrWpFormExtro2);
fileManager->sendContent("");
} else {
fileManager->send(404, F("text/plain"), F("Illegal."));
}
delay(1);
}
// Drag and Drop
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
// https://www.ab-heute-programmieren.de/drag-and-drop-upload-mit-html5/#Schritt_3_Eine_Datei_hochladen
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerReceiverOK(void) {
// Serial.println("fileManagerReceiverOK");
fileManager->send(200);
delay(1);
}
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerReceiver(void) {
// Serial.println("fileManagerReceiver");
HTTPUpload& upload = fileManager->upload();
// Serial.println("Server upload Status: " + String(upload.status));
if (upload.status == UPLOAD_FILE_START) {
String filename = upload.filename;
if (!filename.startsWith("/")) {
filename = "/" + filename;
}
// Serial.print("handleFileUpload Name: ");
// Serial.println(filename);
if (! ( (_ViewSysFiles) || (allowAccessToThisFile(filename)) ) ) {
filename = "/illegalfilename";
}
// cut length
filename = CheckFileNameLengthLimit(filename);
fsUploadFile = ESPxWebFlMgr_FileSystem.open(subdir + filename, "w");
} else if (upload.status == UPLOAD_FILE_WRITE) {
// Serial.print("handleFileUpload Data: ");
// Serial.println(upload.currentSize);
if (fsUploadFile)
fsUploadFile.write(upload.buf, upload.currentSize);
} else if (upload.status == UPLOAD_FILE_END) {
if (fsUploadFile) {
fsUploadFile.close();
// fsUploadFile = NULL;
}
// Serial.print("handleFileUpload Size: ");
// Serial.println(upload.totalSize);
}
delay(1);
}
struct __attribute__ ((__packed__)) zipFileHeader {
uint32_t signature; // 0x04034b50;
uint16_t versionneeded;
uint16_t bitflags;
uint16_t comp_method;
uint16_t lastModFileTime;
uint16_t lastModFileDate;
uint32_t crc_32;
uint32_t comp_size;
uint32_t uncompr_size;
uint16_t fname_len;
uint16_t extra_field_len;
};
struct __attribute__ ((__packed__)) zipDataDescriptor {
uint32_t signature; // 0x08074b50
uint32_t crc32;
uint32_t comp_size;
uint32_t uncompr_size;
};
struct __attribute__ ((__packed__)) zipEndOfDirectory {
uint32_t signature; // 0x06054b50;
uint16_t nrofdisks;
uint16_t diskwherecentraldirectorystarts;
uint16_t nrofcentraldirectoriesonthisdisk;
uint16_t totalnrofcentraldirectories;
uint32_t sizeofcentraldirectory;
uint32_t ofsetofcentraldirectoryrelativetostartofarchiv;
uint16_t commentlength;
};
struct __attribute__ ((__packed__)) zipCentralDirectoryFileHeader {
uint32_t signature; // 0x02014b50
uint16_t versionmadeby;
uint16_t versionneededtoextract;
uint16_t flag;
uint16_t compressionmethode;
uint16_t lastModFileTime;
uint16_t lastModFileDate;
uint32_t crc_32;
uint32_t comp_size;
uint32_t uncompr_size;
uint16_t fname_len;
uint16_t extra_len;
uint16_t comment_len;
uint16_t diskstart;
uint16_t internalfileattr;
uint32_t externalfileattr;
uint32_t relofsoflocalfileheader;
// nun filename, extra field, comment
};
//*****************************************************************************************************
int ESPxWebFlMgr::WriteChunk(const char* b, size_t l) {
// Serial.print(" Chunk: " + String(l) + " ");
const char * footer = "\r\n";
char chunkSize[11];
sprintf(chunkSize, "%zx\r\n", l);
fileManager->client().write(chunkSize, strlen(chunkSize));
fileManager->client().write(b, l);
fileManager->client().write(footer, 2);
return strlen(chunkSize) + l + 2;
}
//*****************************************************************************************************
/* https://en.wikipedia.org/wiki/Zip_(file_format)
https://www.fileformat.info/tool/hexdump.htm
https://hexed.it/?hl=de
HxD https://mh-nexus.de/de/
This code needs some memory:
4 * <nr. of files> + copybuffersize
Uses no compression, because, well, code size. Should be good for 4mb.
*/
void ESPxWebFlMgr::getAllFilesInOneZIP(void) {
const byte copybuffersize = 100;
fileManager->setContentLength(CONTENT_LENGTH_UNKNOWN);
// fileManager->sendHeader(F("Content-Type"), F("text/text"));
// fileManager->sendHeader(F("Transfer-Encoding"), F("chunked"));
// fileManager->sendHeader(F("Connection"), F("close"));
fileManager->sendHeader(F("Content-Disposition"), F("attachment; filename=alles.zip"));
fileManager->sendHeader(F("Content-Transfer-Encoding"), F("binary"));
fileManager->send(200, F("application/octet-stream"), "");
// Pass 0: count files
int files = 0;
{
Dir dir;
File file = firstFile(dir);
while (file) {
String fn = file.name();
if (!file.isDirectory() && (file.size() != 0)) { //jz
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
files++;
}
}
file = nextFile(dir);
}
//Serial.println("Files: " + String(files));
}
// Store the crcs
uint32_t crc_32s[files];
// Pass 1: local headers + file
{
zipFileHeader zip;
zip.signature = 0x04034b50;
zip.versionneeded = 0;
zip.bitflags = 1 << 3;
zip.comp_method = 0; // stored
zip.lastModFileTime = 0x4fa5;
zip.lastModFileDate = 0xe44e;
zip.extra_field_len = 0;
int i = 0;
Dir dir;
File file = firstFile(dir);
while (file) {
String fn = file.name();
if (!file.isDirectory() && (file.size() != 0) ) { //jz
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
if (fn.indexOf("/") == 0) {
fn.remove(0, 1); // "/" filenames beginning with "/" dont work for Windows....
}
zip.comp_size = 0;
zip.uncompr_size = 0;
zip.crc_32 = 0;
zip.fname_len = fn.length();
WriteChunk((char*)&zip, sizeof(zip));
WriteChunk(fn.c_str(), zip.fname_len);
//Serial.print("Send: " + fn);
// File f = dir.open("r",FILE_READ);
int len = file.size();
//Serial.print("\nsending "); Serial.print(fn);
//Serial.print(" len is "); Serial.println(len);
// send crc+len later...
zipDataDescriptor datadiscr;
datadiscr.signature = 0x08074b50;
datadiscr.comp_size = len;
datadiscr.uncompr_size = len;
const char * footer = "\r\n";
char chunkSize[11];
sprintf(chunkSize, "%zx\r\n", len);
fileManager->client().write(chunkSize, strlen(chunkSize));
{ // pff.
CRC32 crc;
byte b[copybuffersize];
int lenr = len;
while (lenr > 0) {
byte r = (lenr > copybuffersize) ? copybuffersize : lenr;
file.read(b, r);
crc.update(b, r);
fileManager->client().write(b, r);
lenr -= r;
// Serial.print(lenr);Serial.print(","); //jz
}
//Serial.println(" done");
datadiscr.crc32 = crc.finalize();
crc_32s[i] = datadiscr.crc32;
}
fileManager->client().write(footer, 2);
WriteChunk((char*)&datadiscr, sizeof(datadiscr));
// f.close();
i++;
/** /
Serial.print(" ");
Serial.print(l);
Serial.println();
/**/
}
}
file = nextFile(dir);
}
}
// Pass 2: Central directory Structur
{
zipEndOfDirectory eod;
eod.signature = 0x06054b50;
eod.nrofdisks = 0;
eod.diskwherecentraldirectorystarts = 0;
eod.nrofcentraldirectoriesonthisdisk = 0;
eod.totalnrofcentraldirectories = 0;
eod.sizeofcentraldirectory = 0;
eod.ofsetofcentraldirectoryrelativetostartofarchiv = 0;
eod.commentlength = 0;
zipCentralDirectoryFileHeader CDFH;
CDFH.signature = 0x02014b50;
CDFH.versionmadeby = 0;
CDFH.versionneededtoextract = 0;
CDFH.flag = 0;
CDFH.compressionmethode = 0; // Stored
CDFH.lastModFileTime = 0x4fa5;
CDFH.lastModFileDate = 0xe44e;
CDFH.extra_len = 0;
CDFH.comment_len = 0;
CDFH.diskstart = 0;
CDFH.internalfileattr = 0x01;
CDFH.externalfileattr = 0x20;
CDFH.relofsoflocalfileheader = 0;
int i = 0;
Dir dir;
File file = firstFile(dir);
while (file) {
String fn = file.name();
if (!file.isDirectory() && (file.size() != 0)) { //jz
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
if (fn.indexOf("/") == 0) {
fn.remove(0, 1); // "/" filenames beginning with "/" dont work for Windows....
}
// Serial.print("CDFH: " + fn);
// File f = dir.open("r",FILE_READ);
int len = file.size();
//Serial.print("\nsending "); Serial.print(fn); //jz
//Serial.print(" len is "); Serial.println(len);
CDFH.comp_size = len;
CDFH.uncompr_size = len;
CDFH.fname_len = fn.length();
CDFH.crc_32 = crc_32s[i];
// f.close();
WriteChunk((char*)&CDFH, sizeof(CDFH));
WriteChunk(fn.c_str(), CDFH.fname_len);
int ofs = sizeof(zipFileHeader) + len + CDFH.fname_len + sizeof(zipDataDescriptor);
// next position
CDFH.relofsoflocalfileheader += ofs;
// book keeping
eod.nrofcentraldirectoriesonthisdisk++;
eod.totalnrofcentraldirectories++;
eod.ofsetofcentraldirectoryrelativetostartofarchiv += ofs;
eod.sizeofcentraldirectory += sizeof(CDFH) + CDFH.fname_len;
i++;
}
}
file = nextFile(dir);
}
// Serial.print("EOD: ");
WriteChunk((char*)&eod, sizeof(eod));
// Serial.println();
}
const char * endchunk = "0\r\n\r\n";
fileManager->client().write(endchunk, 5);
fileManager->sendContent("");
delay(1);
}
//*****************************************************************************************************
void ESPxWebFlMgr::fileManagerCommandExecutor(void) {
// https://www.youtube.com/watch?v=KSxTxynXiBs
/*
for (uint8_t i = 0; i < fileManager->args(); i++) {
Serial.print(i);
Serial.print(" ");
Serial.print(fileManager->argName(i));
Serial.print(": ");
Serial.print(fileManager->arg(i));
Serial.println();
}
*/
// no Args: DIE!
if (fileManager->args() == 0) {
return;
}
// +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
// one arg, "za", zip all and download
if ( (fileManager->args() == 1) && (fileManager->argName(0) == "za") ) {
getAllFilesInOneZIP();
// does it all
return;
}
// +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
// one arg, "dwn", download
// must happen in the context of the webpage... thus via "window.location.href="/c?dwn="+filename;"
if ( (fileManager->args() == 1) && (fileManager->argName(0) == "dwn") ) {
String fn = fileManager->arg(0);
Serial.println(fn);
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
//Serial.println("allowed");
File f = ESPxWebFlMgr_FileSystem.open("/" + fn, "r"); // add slash for esp32_arduino 2.0
if (f) {
//Serial.println("got it open");
fileManager->sendHeader(F("Content-Type"), F("text/text"));
fileManager->sendHeader(F("Connection"), F("close"));
//Serial.print(">");Serial.print(fn);Serial.println("<");
//Serial.println(fn.indexOf("/"));
if (fn.indexOf("/") == 0) {
fileManager->sendHeader(F("Content-Disposition"), "attachment; filename=" + fn.substring(1));
} else {
fileManager->sendHeader(F("Content-Disposition"), "attachment; filename=" + fn);
}
fileManager->sendHeader(F("Content-Transfer-Encoding"), F("binary"));
if (fileManager->streamFile(f, "application/octet-stream") != f.size()) {
Serial.println(F("Sent less data than expected!"));
}
f.close();
return;
} else {
Serial.print("Could not open file "); Serial.println(fn);
}
}
}
// +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
// one arg, "opd", opendirectory
if ( (fileManager->args() == 1) && (fileManager->argName(0) == "opd") ) {
String fn = fileManager->arg(0);
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
delay(1);
}
}
// +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
// one arg, "del", delete
if ( (fileManager->args() == 1) && (fileManager->argName(0) == "del") ) {
String fn = fileManager->arg(0);
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
int x = ESPxWebFlMgr_FileSystem.remove( subdir + "/" + fn); // Add slash
//delay(1000);
if (!x) {
Serial.print("remove failed, try rmdir ");
Serial.print( subdir + "/" + fn );
int y = ESPxWebFlMgr_FileSystem.rmdir( "/" + fn); // Add slash
//delay(1000);
if (!y) {
Serial.print("rmdir failed, directory must be empty! ");
}
}
}
}
// +--++--++--++--++--++--++--++--++--++--++--++--++--++--++--+
// one arg, "ren", rename
if ( (fileManager->args() == 2) && (fileManager->argName(0) == "ren") ) {
String fn = fileManager->arg(0);
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn)) ) {
String fn2 = CheckFileNameLengthLimit(fileManager->arg(1));
if ( (_ViewSysFiles) || (allowAccessToThisFile(fn2)) ) {
Serial.println(subdir);
Serial.println(fn);
Serial.println(fn2);
ESPxWebFlMgr_FileSystem.rename( subdir + "/" + fn, subdir + "/" + fn2);
}
}
}
// dummy answer
fileManager->send(200, "text/plain", "");
delay(1);
}

162
vA1/ESPxWebFlMgr.h 100644
Wyświetl plik

@ -0,0 +1,162 @@
// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr
// Jan 12, 2022 - adds dates/times to display
// based on https://github.com/holgerlembke/ESPxWebFlMgr
// inline guard. Did I mention that c/c++ is broken by design?
#ifndef ESPxWebFlMgr_h
#define ESPxWebFlMgr_h
/*
Changes
V1.03
x removed all SPIFFS from ESP32 version, switched fully to LittleFS
x fixed rename+delete for ESP32+LittleFS (added "/")
V1.02
x fixed the way to select the file system by conditional defines
V1.01
+ added file name progress while uploading
x fixed error in ZIP file structure (zip.bitflags needs a flag)
V1.00
+ out of V0.9998...
+ ESP8266: LittleFS is default
+ javascript: added "msgline();"
+ javascript: added "Loading..." as a not-working-hint to show that Javascript is disabled
+ cleaning up the "/"-stuff (from SPIFF with leading "/" to LittleFS without...)
+ Warning: esp8266 2.7.4 has an error in mime::getContentType(path) for .TXT. Fix line 65 is { kTxtSuffix, kTxt },
+ review of "edit file", moved some stuff to ESPxWebFlMgrWpF.h
*/
#include <Arduino.h>
#include <inttypes.h>
// file system default for esp8266 is LittleFS, for ESP32 it is SPIFFS (no time to check...)
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <FS.h>
//
#include <LittleFS.h>
#define ESPxWebFlMgr_FileSystem LittleFS
/*
#include <SPIFFS.h>
#define ESPxWebFlMgr_FileSystem SPIFFS
*/
#endif
#ifdef ESP32
#include <WiFi.h>
#include <WebServer.h>
#include <FS.h>
#include <SD_MMC.h> //jz #include <LittleFS.h>
#define ESPxWebFlMgr_FileSystem SD_MMC //jz #define ESPxWebFlMgr_FileSystem LittleFS
#endif
#ifndef ESPxWebFlMgr_FileSystem
#pragma message ("ESPxWebFlMgr_FileSystem not defined.")
#endif
/* undefine this to save about 10k code space.
it requires to put the files from "<library>/filemanager" into the FS. No free lunch.
*/
#define fileManagerServerStaticsInternally
// will show the Edit-Button for every file type, even binary and such.
//#define fileManagerEditEverything
class ESPxWebFlMgr {
private:
word _Port ;
#ifdef ESP8266
ESP8266WebServer * fileManager = NULL;
#endif
#ifdef ESP32
WebServer * fileManager = NULL;
#endif
bool _ViewSysFiles = false;
String _SysFileStartPattern = "/.";
File fsUploadFile;
String _backgroundColor = "black";
void fileManagerNotFound(void);
String dispIntDotted(size_t i);
String dispFileString(size_t fs);
String CheckFileNameLengthLimit(String fn);
// the webpage
void fileManagerIndexpage(void);
void fileManagerJS(void);
void fileManagerCSS(void);
void fileManagerGetBackGround(void);
// javascript xmlhttp includes
String colorline(int i);
String escapeHTMLcontent(String html);
void fileManagerFileListInsert(void);
void fileManagerFileEditorInsert(void);
boolean allowAccessToThisFile(const String filename);
void fileManagerCommandExecutor(void);
void fileManagerReceiverOK(void);
void fileManagerReceiver(void);
// Zip-File uncompressed/stored
void getAllFilesInOneZIP(void);
int WriteChunk(const char* b, size_t l);
// helper: fs.h from esp32 and esp8266 don't have a compatible solution
// for getting a file list from a directory
#ifdef ESP32
#define Dir File
#endif
File nextFile(Dir &dir);
File firstFile(Dir &dir);
// and not to get this data about usage...
size_t totalBytes(void);
size_t usedBytes(void);
public:
ESPxWebFlMgr(word port);
virtual ~ESPxWebFlMgr();
void begin();
void end();
virtual void handleClient();
// This must be called before the webpage is loaded in the browser...
// must be a valid css color name, see https://en.wikipedia.org/wiki/Web_colors
void setBackGroundColor(const String backgroundColor);
void setViewSysFiles(bool vsf);
bool getViewSysFiles(void);
void setSysFileStartPattern(String sfsp);
String getSysFileStartPattern(void);
};
#endif
/*
History
-- 2019-07-07
+ Renamed to ESPxWebFlMgr and made it work with esp32 and esp8266
+ separated file manager web page, "build script" to generate it
-- 2019-07-06
+ "Download all files" creates a zip file from all files and downloads it
+ option to set background color
- html5 fixes
-- 2019-07-03
+ Public Release on https://github.com/holgerlembke/ESP8266WebFlMgr
Things to do
?? unify file system access for SPIFFS, LittleFS and SDFS
*/

Wyświetl plik

@ -0,0 +1,541 @@
// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr
// Jan 12, 2022 - adds dates/times to display
// based on https://github.com/holgerlembke/ESPxWebFlMgr
// inline guard. Did I mention that c/c++ is broken by design?
#ifndef ESPxWebFlMgrWp_h
#define ESPxWebFlMgrWp_h
// this file has been created by makeESPxWebFlMgrWp\do.cmd
//*****************************************************************************************************
static const char ESPxWebFlMgrWpindexpage[] PROGMEM = R"==x==(
<!DOCTYPE html>
<html lang="en">
<head>
<title>FileManager</title>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="/bg.css">
<link rel="stylesheet" type="text/css" href="/fm.css">
<script src="/fm.js"></script>
<script src="/gzipper.js"></script>
</head>
<body class="background">
<div id="gc">
<div class="o1">&nbsp;</div>
<div class="o2">&nbsp;</div>
<div class="o3" id="o3">&nbsp;</div>
<div class="o4">&nbsp;</div>
<div class="m1">
<div class="s11">&nbsp;</div>
<div class="s12">
<div class="s13 background">&nbsp;</div>
</div>
</div>
<div class="m2" ondrop="dropHandler(event);" ondragover="dragOverHandler(event);">
File<br />
Drop<br />
Zone<br />
</div>
<div class="m3">
<div class="s31">&nbsp;</div>
<div class="s32">
<div class="s33 background">&nbsp;</div>
</div>
</div>
<div class="u1">&nbsp;</div>
<div class="u2" onclick="downloadall();">Download all files</div>
<div class="u3" id="msg">Loading...</div>
<div class="u4">&nbsp;</div>
<div class="c" id="fi">
File list should appear here.
</div>
</div>
</body>
</html>
)==x==";
static const char ESPxWebFlMgrWpjavascript[] PROGMEM = R"==x==(
function compressurlfile(source) {
msgline("Fetching file...");
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
var data = this.responseText;
var gzip = require('gzip-js'), options = { level: 9, name: source, timestamp: parseInt(Date.now() / 1000, 10) };
var out = gzip.zip(data, options);
var bout = new Uint8Array(out); // out is 16 bits...
msgline("Sending compressed file...");
var sendback = new XMLHttpRequest();
sendback.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
getfileinsert();
}
};
sendback.open('POST', '/r');
var formdata = new FormData();
var blob = new Blob([bout], { type: "application/octet-binary" });
formdata.append(source + '.gz', blob, source + '.gz');
sendback.send(formdata);
}
};
request.open('GET', source, true);
request.send(null);
}
var subdir;
function getfileinsert() {
msgline("Fetching files infos...");
subdir = '/';
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
var res = this.responseText.split("##");
document.getElementById('fi').innerHTML = res[0];
document.getElementById("o3").innerHTML = res[1];
msgline("");
}
};
request.open('GET', '/i', true);
request.send(null);
}
function getfileinsert2(strddd) {
msgline("Fetching files infos...");
subdir = strddd;
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
var res = this.responseText.split("##");
document.getElementById('fi').innerHTML = res[0];
document.getElementById("o3").innerHTML = res[1];
msgline("");
}
};
request.open('GET', '/i?subdir=' + strddd, true); // must send the subdir variable to get that folder //jz
request.send(null);
}
function executecommand(command) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
getfileinsert2(subdir);
}
};
xhr.open('GET', '/c?' + command, true);
xhr.send(null);
}
function downloadfile(filename) {
window.location.href = "/c?dwn=" + filename;
}
function opendirectory(filename) {
getfileinsert2(filename);
}
function deletefile(filename) {
if (confirm("Really delete " + filename)) {
msgline("Refresh when done deleting..."); //jz msgline("Please wait. Delete in progress...");
executecommand("del=" + filename);
}
}
function renamefile(filename) {
var newname = prompt("new name for " + filename, filename);
if (newname != null) {
msgline("Refresh when done renaming ..."); //jz msgline("Please wait. Rename in progress...");
executecommand("ren=" + filename + "&new=" + newname);
}
}
var editxhr;
function editfile(filename) {
msgline("Please wait. Creating editor...");
editxhr = new XMLHttpRequest();
editxhr.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
document.getElementById('fi').innerHTML = editxhr.responseText;
document.getElementById("o3").innerHTML = "Edit " + filename;
msgline("");
}
};
editxhr.open('GET', '/e?edit=' + filename, true);
editxhr.send(null);
}
function sved(filename) {
var content = document.getElementById('tect').value;
// utf-8
content = unescape(encodeURIComponent(content));
var xhr = new XMLHttpRequest();
xhr.open("POST", "/r", true);
var boundary = '-----whatever';
xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
var body = "" +
'--' + boundary + '\r\n' +
'Content-Disposition: form-data; name="uploadfile"; filename="' + filename + '"' + '\r\n' +
'Content-Type: text/plain' + '\r\n' +
'' + '\r\n' +
content + '\r\n' +
'--' + boundary + '--\r\n' + // \r\n fixes upload delay in ESP8266WebServer
'';
// ajax does not do xhr.setRequestHeader("Content-length", body.length);
xhr.onreadystatechange = function () {
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
getfileinsert();
}
}
xhr.send(body);
}
function abed() {
getfileinsert();
}
var uploaddone = true; // hlpr for multiple file uploads
function uploadFile(file, islast) {
uploaddone = false;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
// console.log(xhr.status);
var DONE = this.DONE || 4;
if (this.readyState === DONE) {
if (islast) {
getfileinsert2(subdir);
console.log('last file');
}
uploaddone = true;
}
};
xhr.open('POST', '/r');
var formdata = new FormData();
//var newname = subdir + '/' + file.name; //jz didnt work, so do it in c++
//file.name = newname;
formdata.append('uploadfile', file);
// not sure why, but with that the upload to esp32 is stable.
formdata.append('dummy', 'dummy');
xhr.send(formdata);
}
var globaldropfilelisthlpr = null; // read-only-list, no shift()
var transferitem = 0;
var uploadFileProzessorhndlr = null;
function uploadFileProzessor() {
if (uploaddone) {
if (transferitem==globaldropfilelisthlpr.length) {
clearInterval(uploadFileProzessorhndlr);
} else {
var file = globaldropfilelisthlpr[transferitem];
msgline("Please wait. Transferring file "+file.name+"...");
console.log('process file ' + file.name);
transferitem++;
uploadFile(file,transferitem==globaldropfilelisthlpr.length);
}
}
}
/*
function dropHandlerALT(ev) {
console.log('File(s) dropped');
document.getElementById('msg').innerHTML = "Please wait. Transferring file...";
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (ev.dataTransfer.items[i].kind === 'file') {
var file = ev.dataTransfer.items[i].getAsFile();
uploadFile(file);
console.log('.1. file[' + i + '].name = ' + file.name);
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
console.log('.2. file[' + i + '].name = ' + ev.dataTransfer.files[i].name);
}
}
}
*/
function dropHandler(ev) {
console.log('File(s) dropped');
globaldropfilelisthlpr = ev.dataTransfer;
transferitem = 0;
msgline("Please wait. Transferring file...");
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
if (ev.dataTransfer.items) {
var data = ev.dataTransfer;
globaldropfilelisthlpr = data.files;
uploadFileProzessorhndlr = setInterval(uploadFileProzessor,1000);
console.log('Init upload list.');
} else {
// Use DataTransfer interface to access the file(s)
for (var i = 0; i < ev.dataTransfer.files.length; i++) {
console.log('.2. file[' + i + '].name = ' + ev.dataTransfer.files[i].name);
}
}
}
function dragOverHandler(ev) {
console.log('File(s) in drop zone');
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
}
function msgline(msg) {
document.getElementById('msg').innerHTML = msg;
}
function downloadall() {
msgline("Sending all files in one zip.");
window.location.href = "/c?za=all";
msgline("");
}
//->
window.onload = getfileinsert;
)==x==";
//*****************************************************************************************************
static const char ESPxWebFlMgrWpcss[] PROGMEM = R"==g==(
div {
margin: 1px;
padding: 0px;
font-family: 'Segoe UI', Verdana, sans-serif;
}
#gc {
display: grid;
grid-template-columns: 80px 25% auto 30px;
grid-template-rows: 20px 30px auto 30px 20px;
grid-template-areas: "o1 o2 o3 o4" "m1 c c c" "m2 c c c" "m3 c c c" "u1 u2 u3 u4";
}
.o1 {
grid-area: o1;
background-color: #9999CC;
border-top-left-radius: 20px;
margin-bottom: 0px;
}
.o2 {
grid-area: o2;
background-color: #9999FF;
margin-bottom: 0px;
}
.o3 {
grid-area: o3;
background-color: #CC99CC;
margin-bottom: 0px;
white-space: nowrap;
}
.o4 {
grid-area: o4;
background-color: #CC6699;
border-radius: 0 10px 10px 0;
margin-bottom: 0px;
}
.m1 {
grid-area: m1;
margin-top: 0px;
background-color: #9999CC;
display: grid;
grid-template-columns: 60px 20px;
grid-template-rows: 20px;
grid-template-areas: "s11 s12";
}
.s12 {
margin: 0px;
background-color: #9999CC;
}
.s13 {
margin: 0px;
border-top-left-radius: 20px;
height: 30px;
}
.m2 {
display: flex;
justify-content: center;
align-items: center;
grid-area: m2;
background-color: #CC6699;
width: 60px;
}
.m3 {
grid-area: m3;
margin-bottom: 0px;
background-color: #9999CC;
display: grid;
grid-template-columns: 60px 20px;
grid-template-rows: 20px;
grid-template-areas: "s31 s32";
}
.s32 {
margin: 0px;
background-color: #9999CC;
}
.s33 {
margin: 0px;
border-bottom-left-radius: 20px;
height: 30px;
}
.u1 {
grid-area: u1;
background-color: #9999CC;
border-bottom-left-radius: 20px;
margin-top: 0px;
}
.u2 {
grid-area: u2;
cursor: pointer;
background-color: #CC6666;
margin-top: 0px;
padding-left: 10px;
vertical-align: middle;
font-size: 80%;
}
.u2:hover {
background-color: #9999FF;
color: white;
}
.u3 {
grid-area: u3;
padding-left: 10px;
background-color: #FF9966;
font-size: 80%;
margin-top: 0px;
}
.u4 {
grid-area: u4;
background-color: #FF9900;
border-radius: 0 10px 10px 0;
margin-top: 0px;
}
.c {
grid-area: c;
}
#fi .b {
background-color: Transparent;
border: 1px solid #9999FF;
border-radius: 1px;
padding: 0px;
width: 30px;
cursor: pointer;
}
#fi .b:hover {
background-color: #9999FF;
color: white;
}
.cc {
width: min-content;
margin: 10px 0px;
}
.gc div {
padding: 1px;
}
.ccg {
height: 1.5em;
background-color: #A5A5FF;
}
.ccu {
height: 1.5em;
background-color: #FE9A00;
}
.ccd {
height: 1.5em;
background-color: #e8e2d8;
}
.ccl {
border-radius: 5px 0 0 5px;
cursor: pointer;
}
.ccl:hover {
border-radius: 5px 0 0 5px;
color: white;
cursor: pointer;
}
.ccr {
border-radius: 0 5px 5px 0;
}
.cct {
text-align: right;
}
.ccz {
text-align: right;
}
.gc {
display: grid;
grid-template-columns: repeat(4, max-content);
}
)==g==";
#endif

Wyświetl plik

@ -0,0 +1,23 @@
// mods by James Zahary Dec 28, 2021 https://github.com/jameszah/ESPxWebFlMgr
// based on https://github.com/holgerlembke/ESPxWebFlMgr
// inline guard. Still broken by design?
#ifndef ESPxWebFlMgrWpF_h
#define ESPxWebFlMgrWpF_h
static const char ESPxWebFlMgrWpFormIntro[] PROGMEM =
R"==x==(<form><textarea id="tect" rows="25" cols="80">)==x==";
static const char ESPxWebFlMgrWpFormExtro1[] PROGMEM =
R"==x==(</textarea></form><button title="Save file" onclick="sved(')==x==";
// noot sure what the <script> part is for.
static const char ESPxWebFlMgrWpFormExtro2[] PROGMEM =
R"==x==(');" >Save</button>&nbsp;<button title="Abort editing" onclick="abed();">Abort editing</button>
<script id="info">document.getElementById('o3').innerHTML = "File:";</script>)==x==";
#endif

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,968 @@
/*
Copyright (c) 2018 Brian Lough. All right reserved.
UniversalTelegramBot - Library to create your own Telegram Bot using
ESP8266 or ESP32 on Arduino IDE.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
**** Note Regarding Client Connection Keeping ****
Client connection is established in functions that directly involve use of
client, i.e sendGetToTelegram, sendPostToTelegram, and
sendMultipartFormDataToTelegram. It is closed at the end of
sendMultipartFormDataToTelegram, but not at the end of sendGetToTelegram and
sendPostToTelegram as these may need to keep the connection alive for respose
/ response checking. Re-establishing a connection then wastes time which is
noticeable in user experience. Due to this, it is important that connection
be closed manually after calling sendGetToTelegram or sendPostToTelegram by
calling closeClient(); Failure to close connection causes memory leakage and
SSL errors
*/
// James Zahary June 30, 2020
// - small mods to add caption to photos, and slow down transmit to telegram
// James Zahary Jan 2, 2022
// - Client to WiFiClientSecure due to esp32-arduino 2.02
#include "UniversalTelegramBot.h"
UniversalTelegramBot::UniversalTelegramBot(String token, WiFiClientSecure &client) { // UniversalTelegramBot::UniversalTelegramBot(String token, Client &client) {
_token = token;
#ifdef ARDUINO_ESP8266_RELEASE_2_5_0
//client->setInsecure();
#endif
this->client = &client;
}
String UniversalTelegramBot::sendGetToTelegram(String command) {
String mess = "";
long now;
bool avail;
// Connect with api.telegram.org if not already connected
if (!client->connected()) {
#ifdef _debug
Serial.println(F("[BOT]Connecting to server"));
#endif
if (!client->connect(HOST, SSL_PORT)) {
#ifdef _debug
Serial.println(F("[BOT]Conection error"));
#endif
}
}
if (client->connected()) {
#ifdef _debug
Serial.println(F(".... connected to server"));
#endif
String a = "";
char c;
int ch_count = 0;
client->println("GET /" + command);
now = millis();
avail = false;
while (millis() - now < longPoll * 1000 + waitForResponse) {
while (client->available()) {
char c = client->read();
if (ch_count < maxMessageLength) {
mess = mess + c;
ch_count++;
}
avail = true;
}
if (avail) {
#ifdef _debug
Serial.println();
Serial.println(mess);
Serial.println();
#endif
break;
}
}
}
return mess;
}
String UniversalTelegramBot::sendPostToTelegram(String command, JsonObject payload) {
String body = "";
String headers = "";
long now;
bool responseReceived = false;
// Connect with api.telegram.org if not already connected
if (!client->connected()) {
#ifdef _debug
Serial.println(F("[BOT Client]Connecting to server"));
#endif
if (!client->connect(HOST, SSL_PORT)) {
#ifdef _debug
Serial.println(F("[BOT Client]Conection error"));
#endif
}
}
if (client->connected()) {
// POST URI
client->print("POST /" + command);
client->println(F(" HTTP/1.1"));
delay(jzdelay);
// Host header
client->print(F("Host:"));
client->println(HOST);
delay(jzdelay);
// JSON content type
client->println(F("Content-Type: application/json"));
delay(jzdelay);
// Content length
int length = measureJson(payload);
client->print(F("Content-Length:"));
client->println(length);
delay(jzdelay);
// End of headers
client->println();
// POST message body
String out;
serializeJson(payload, out);
client->println(out);
delay(jzdelay);
int ch_count = 0;
now = millis();
bool finishedHeaders = false;
bool currentLineIsBlank = true;
while (millis() - now < waitForResponse) {
while (client->available()) {
char c = client->read();
responseReceived = true;
if (!finishedHeaders) {
if (currentLineIsBlank && c == '\n') {
finishedHeaders = true;
} else {
headers = headers + c;
}
} else {
if (ch_count < maxMessageLength) {
body = body + c;
ch_count++;
}
}
if (c == '\n') currentLineIsBlank = true;
else if (c != '\r') currentLineIsBlank = false;
}
if (responseReceived && ch_count > 5) { //jz
#ifdef _debug
Serial.println();
Serial.println(body);
Serial.println();
#endif
//Serial.print(millis() - now); Serial.println(" sendPostToTelegram - breaking");
break;
}
}
}
return body;
}
String UniversalTelegramBot::sendMultipartFormDataToTelegram(
String command, String binaryProperyName, String fileName,
String contentType, String chat_id, int fileSize,
MoreDataAvailable moreDataAvailableCallback,
GetNextByte getNextByteCallback,
GetNextBuffer getNextBufferCallback,
GetNextBufferLen getNextBufferLenCallback) {
String body = "";
String headers = "";
long now;
bool responseReceived = false;
bool finishedHeaders = false;
bool currentLineIsBlank = true;
String boundry = F("------------------------b8f610217e83e29b");
// Connect with api.telegram.org if not already connected
if (!client->connected()) {
#ifdef _debug
Serial.println(F("[BOT Client]Connecting to server"));
#endif
if (!client->connect(HOST, SSL_PORT)) {
#ifdef _debug
Serial.println(F("[BOT Client]Conection error"));
#endif
}
}
if (client->connected()) {
String start_request = "";
String end_request = "";
start_request = start_request + "--" + boundry + "\r\n";
start_request = start_request + "content-disposition: form-data; name=\"chat_id\"" + "\r\n";
start_request = start_request + "\r\n";
start_request = start_request + chat_id + "\r\n";
start_request = start_request + "--" + boundry + "\r\n";
start_request = start_request + "content-disposition: form-data; name=\"caption\"" + "\r\n";
start_request = start_request + "\r\n";
start_request = start_request + "caption here!" + "\r\n";
start_request = start_request + "--" + boundry + "\r\n";
start_request = start_request + "content-disposition: form-data; name=\"" + binaryProperyName + "\"; filename=\"" + fileName + "\"" + "\r\n";
start_request = start_request + "Content-Type: " + contentType + "\r\n";
start_request = start_request + "\r\n";
end_request = end_request + "\r\n";
end_request = end_request + "--" + boundry + "--" + "\r\n";
client->print("POST /bot" + _token + "/" + command);
client->println(F(" HTTP/1.1"));
// Host header
client->print(F("Host: "));
client->println(HOST);
client->println(F("User-Agent: arduino/1.0"));
Serial.print("*") ; delay(jzdelay); //jz
client->println(F("Accept: */*"));
Serial.print("*") ; delay(jzdelay); //jz
int contentLength = fileSize + start_request.length() + end_request.length();
#ifdef _debug
Serial.println("Content-Length: " + String(contentLength));
#endif
client->print("Content-Length: ");
client->println(String(contentLength));
client->println("Content-Type: multipart/form-data; boundary=" + boundry);
Serial.print("*") ; delay(jzdelay); //jz
client->println(); //v99 - ssl not happy
Serial.print("*") ; delay(jzdelay); //jz
client->print(start_request);
Serial.print("Start request: " + start_request);
#ifdef _debug
Serial.print("Start request: " + start_request);
#endif
if (getNextByteCallback == nullptr) {
while (moreDataAvailableCallback()) {
client->write((const uint8_t *)getNextBufferCallback(), getNextBufferLenCallback());
#ifdef _debug
Serial.println(F("Sending photo from buffer"));
#endif
}
} else {
#ifdef _debug
Serial.println(F("Sending photo by binary"));
#endif
byte buffer[jzblocksize]; //jz 512
int count = 0;
char ch;
while (moreDataAvailableCallback()) {
buffer[count] = getNextByteCallback();
count++;
if (count == jzblocksize) { //jz 512
// yield();
#ifdef _debug
//Serial.println(F("Sending binary photo full buffer"));
#endif
client->write((const uint8_t *)buffer, jzblocksize); //jz 512
Serial.print("*") ; delay(jzdelay); //jz
count = 0;
}
}
if (count > 0) {
#ifdef _debug
Serial.println(F("Sending binary photo remaining buffer"));
#endif
client->write((const uint8_t *)buffer, count);
Serial.print("*") ; delay(jzdelay); //jz
}
}
client->print(end_request);
//#ifdef _debug
Serial.print("End request: " + end_request);
//#endif
Serial.print("... Done Sending. Client.Available = "); Serial.println(client->available());
delay(2000);
Serial.print("... 2 secs later. Client.Available = "); Serial.println(client->available());
int ch_count = 0;
now = millis();
while (millis() - now < waitForResponse) {
while (client->available()) {
char c = client->read();
responseReceived = true;
if (!finishedHeaders) {
if (currentLineIsBlank && c == '\n') {
finishedHeaders = true;
} else {
headers = headers + c;
}
} else {
if (ch_count < maxMessageLength) {
body = body + c;
ch_count++;
}
}
if (c == '\n') currentLineIsBlank = true;
else if (c != '\r') currentLineIsBlank = false;
}
if (responseReceived && ch_count > 5) { //jz && ch_count > 5
#ifdef _debug
Serial.println();
Serial.println(body);
Serial.println();
#endif
//Serial.print(millis() - now); Serial.println(" sendMultipartFormDataToTelegram - breaking");
break;
}
}
}
closeClient();
return body;
}
String UniversalTelegramBot::sendMultipartFormDataToTelegramWithCaption(
String command, String binaryProperyName, String fileName,
String contentType, String caption, String chat_id, int fileSize,
MoreDataAvailable moreDataAvailableCallback,
GetNextByte getNextByteCallback,
GetNextBuffer getNextBufferCallback,
GetNextBufferLen getNextBufferLenCallback) {
String body = "";
String headers = "";
long now;
bool responseReceived = false;
bool finishedHeaders = false;
bool currentLineIsBlank = true;
String boundry = F("------------------------b8f610217e83e29b");
// Connect with api.telegram.org if not already connected
if (!client->connected()) {
#ifdef _debug
Serial.println(F("[BOT Client]Connecting to server"));
#endif
//client->stop();
//client -> setInsecure(); //a1
//client -> setTimeout(2);
//client -> setHandshakeTimeout(120000); //120000
int x = client->connect(HOST, SSL_PORT);
if (x == 0) {
#ifdef _debug
Serial.println(F("[BOT Client]Conection error"));
delay(2000);
int x = client->connect(HOST, SSL_PORT);
delay(2000);
if (!client->connected()) {
Serial.println(F("[BOT Client]Another Conection error"));
delay(2000);
}
#endif
}
}
if (client->connected()) {
String start_request = "";
String end_request = "";
//Serial.print("Start: "); Serial.println(ESP.getFreeHeap());
start_request = start_request + "--" + boundry + "\r\n";
start_request = start_request + "content-disposition: form-data; name=\"chat_id\"" + "\r\n";
start_request = start_request + "\r\n";
start_request = start_request + chat_id + "\r\n";
start_request = start_request + "--" + boundry + "\r\n"; //jz caption stuff
start_request = start_request + "content-disposition: form-data; name=\"caption\"" + "\r\n";
start_request = start_request + "\r\n";
start_request = start_request + caption + "\r\n";
start_request = start_request + "--" + boundry + "\r\n";
start_request = start_request + "content-disposition: form-data; name=\"" + binaryProperyName + "\"; filename=\"" + fileName + "\"" + "\r\n";
start_request = start_request + "Content-Type: " + contentType + "\r\n";
start_request = start_request + "\r\n";
end_request = end_request + "\r\n";
end_request = end_request + "--" + boundry + "--" + "\r\n";
client->print("POST /bot" + _token + "/" + command);
client->println(F(" HTTP/1.1"));
// Host header
client->print(F("Host: "));
client->println(HOST);
client->println(F("User-Agent: arduino/1.0"));
Serial.print("*") ; delay(jzdelay); //jz
client->println(F("Accept: */*"));
Serial.print("*") ; delay(jzdelay); //jz
int contentLength = fileSize + start_request.length() + end_request.length();
#ifdef _debug
Serial.println("Content-Length: " + String(contentLength));
#endif
client->print("Content-Length: ");
client->println(String(contentLength));
client->println("Content-Type: multipart/form-data; boundary=" + boundry);
Serial.print("*") ; delay(jzdelay); //jz
client->println(); //v99
Serial.print("*") ; delay(jzdelay); //jz
client->print(start_request);
#ifdef _debug
Serial.print("Start request: " + start_request);
#endif
//Serial.print("End: "); Serial.println(ESP.getFreeHeap());
if (getNextByteCallback == nullptr) {
while (moreDataAvailableCallback()) {
client->write((const uint8_t *)getNextBufferCallback(), getNextBufferLenCallback());
#ifdef _debug
Serial.println(F("Sending photo from buffer"));
#endif
}
} else {
#ifdef _debug
Serial.println(F("Sending photo by binary"));
#endif
byte buffer[jzblocksize];
int count = 0;
char ch;
while (moreDataAvailableCallback()) {
buffer[count] = getNextByteCallback();
count++;
if (count == jzblocksize) {
// yield();
#ifdef _debug
//Serial.println(F("Sending binary photo full buffer"));
#endif
client->write((const uint8_t *)buffer, jzblocksize);
Serial.print("*") ; delay(jzdelay); //jz
count = 0;
}
}
if (count > 0) {
#ifdef _debug
Serial.println(F("Sending binary photo remaining buffer"));
#endif
client->write((const uint8_t *)buffer, count);
Serial.print("*") ; delay(jzdelay); //jz
}
}
client->print(end_request);
#ifdef _debug
Serial.print("End request: " + end_request);
Serial.print("... Done Sending. Client.Available = "); Serial.println(client->available());
delay(2000);
Serial.print("... 2 secs later. Client.Available = "); Serial.println(client->available());
#endif
int ch_count = 0;
now = millis();
while (millis() - now < waitForResponse) {
while (client->available()) {
char c = client->read();
responseReceived = true;
if (!finishedHeaders) {
if (currentLineIsBlank && c == '\n') {
finishedHeaders = true;
} else {
headers = headers + c;
}
} else {
if (ch_count < maxMessageLength) {
body = body + c;
ch_count++;
}
}
if (c == '\n') currentLineIsBlank = true;
else if (c != '\r') currentLineIsBlank = false;
}
if (responseReceived && ch_count > 5) { //jz && ch_count > 5
#ifdef _debug
Serial.println();
Serial.println(body);
Serial.println();
#endif
//Serial.print(millis() - now); Serial.println(" sendMultipartFormDataToTelegram - breaking");
break;
}
}
}
closeClient();
return body;
}
bool UniversalTelegramBot::getMe() {
String command = "bot" + _token + "/getMe";
String response = sendGetToTelegram(command); // receive reply from telegram.org
DynamicJsonDocument doc(maxMessageLength);
DeserializationError error = deserializeJson(doc, response);
JsonObject obj = doc.as<JsonObject>(); //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<JsonObject>(); //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<String>();
messages[messageIndex].from_name = message["from"]["first_name"].as<String>();
messages[messageIndex].date = message["date"].as<String>();
messages[messageIndex].chat_id = message["chat"]["id"].as<String>();
messages[messageIndex].chat_title = message["chat"]["title"].as<String>();
if (message.containsKey("text")) {
messages[messageIndex].text = message["text"].as<String>();
} else if (message.containsKey("location")) {
messages[messageIndex].longitude = message["location"]["longitude"].as<float>();
messages[messageIndex].latitude = message["location"]["latitude"].as<float>();
}
} else if (result.containsKey("channel_post")) {
JsonObject message = result["channel_post"];
messages[messageIndex].type = F("channel_post");
messages[messageIndex].text = message["text"].as<String>();
messages[messageIndex].date = message["date"].as<String>();
messages[messageIndex].chat_id = message["chat"]["id"].as<String>();
messages[messageIndex].chat_title = message["chat"]["title"].as<String>();
} 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<String>();
messages[messageIndex].from_name = message["from"]["first_name"].as<String>();
messages[messageIndex].text = message["data"].as<String>();
messages[messageIndex].date = message["date"].as<String>();
messages[messageIndex].chat_id = message["message"]["chat"]["id"].as<String>();
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<String>();
messages[messageIndex].from_name = message["from"]["first_name"].as<String>();
messages[messageIndex].date = message["date"].as<String>();
messages[messageIndex].chat_id = message["chat"]["id"].as<String>();
messages[messageIndex].chat_title = message["chat"]["title"].as<String>();
if (message.containsKey("text")) {
messages[messageIndex].text = message["text"].as<String>();
} else if (message.containsKey("location")) {
messages[messageIndex].longitude = message["location"]["longitude"].as<float>();
messages[messageIndex].latitude = message["location"]["latitude"].as<float>();
}
}
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<JsonObject>());
}
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<JsonArray>();
// 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<JsonObject>());
}
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<JsonArray>();
return sendPostMessage(payload.as<JsonObject>());
}
/***********************************************************************
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<JsonArray>();
}
return sendPostPhoto(payload.as<JsonObject>());
}
bool UniversalTelegramBot::checkForOkResponse(String response) {
int responseLength = response.length();
for (int m = 5; m < responseLength + 1; m++) {
if (response.substring(m - 10, m) ==
"{\"ok\":true") { // Chek if message has been properly sent
return true;
}
}
return false;
}
bool UniversalTelegramBot::sendChatAction(String chat_id, String text) {
bool sent = false;
#ifdef _debug
Serial.println(F("SEND Chat Action Message"));
#endif
long sttime = millis();
if (text != "") {
while (millis() < sttime + 8000) { // loop for a while to send the message
String command = "bot" + _token + "/sendChatAction?chat_id=" + chat_id +
"&action=" + text;
String response = sendGetToTelegram(command);
#ifdef _debug
Serial.println(response);
#endif
sent = checkForOkResponse(response);
if (sent) break;
}
}
closeClient();
return sent;
}
void UniversalTelegramBot::closeClient() {
if (client->connected()) {
#ifdef _debug
Serial.println(F("Closing client"));
#endif
client->stop();
client -> setHandshakeTimeout(120000); // bug in esp32-arduino 2.0.2
}
}

Wyświetl plik

@ -0,0 +1,129 @@
/*
Copyright (c) 2018 Brian Lough. All right reserved.
UniversalTelegramBot - Library to create your own Telegram Bot using
ESP8266 or ESP32 on Arduino IDE.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef UniversalTelegramBot_h
#define UniversalTelegramBot_h
#define ARDUINOJSON_DECODE_UNICODE 1
#define ARDUINOJSON_USE_LONG_LONG 1
#include <Arduino.h>
#include <ArduinoJson.h>
#include <Client.h>
#include <core_version.h>
// a1
#include <WiFiClientSecure.h>
#define HOST "api.telegram.org"
#define SSL_PORT 443
#define HANDLE_MESSAGES 1
//unmark following line to enable debug mode
//#define _debug
typedef bool (*MoreDataAvailable)();
typedef byte (*GetNextByte)();
typedef byte* (*GetNextBuffer)();
typedef int (GetNextBufferLen)();
struct telegramMessage {
String text;
String chat_id;
String chat_title;
String from_id;
String from_name;
String date;
String type;
float longitude;
float latitude;
int update_id;
};
class UniversalTelegramBot {
public:
UniversalTelegramBot(String token, WiFiClientSecure &client); //UniversalTelegramBot(String token, Client &client);
String sendGetToTelegram(String command);
String sendPostToTelegram(String command, JsonObject payload);
String
sendMultipartFormDataToTelegram(String command, String binaryProperyName,
String fileName, String contentType,
String chat_id, int fileSize,
MoreDataAvailable moreDataAvailableCallback,
GetNextByte getNextByteCallback,
GetNextBuffer getNextBufferCallback,
GetNextBufferLen getNextBufferLenCallback);
String
sendMultipartFormDataToTelegramWithCaption(String command, String binaryProperyName,
String fileName, String contentType,
String caption, String chat_id, int fileSize,
MoreDataAvailable moreDataAvailableCallback,
GetNextByte getNextByteCallback,
GetNextBuffer getNextBufferCallback,
GetNextBufferLen getNextBufferLenCallback);
bool getMe();
bool sendSimpleMessage(String chat_id, String text, String parse_mode);
bool sendMessage(String chat_id, String text, String parse_mode = "");
bool sendMessageWithReplyKeyboard(String chat_id, String text,
String parse_mode, String keyboard,
bool resize = false, bool oneTime = false,
bool selective = false);
bool sendMessageWithInlineKeyboard(String chat_id, String text,
String parse_mode, String keyboard);
bool sendChatAction(String chat_id, String text);
bool sendPostMessage(JsonObject payload);
String sendPostPhoto(JsonObject payload);
String sendPhotoByBinary(String chat_id, String contentType, int fileSize,
MoreDataAvailable moreDataAvailableCallback,
GetNextByte getNextByteCallback,
GetNextBuffer getNextBufferCallback,
GetNextBufferLen getNextBufferLenCallback);
String sendPhoto(String chat_id, String photo, String caption = "",
bool disable_notification = false,
int reply_to_message_id = 0, String keyboard = "");
int getUpdates(long offset);
bool checkForOkResponse(String response);
telegramMessage messages[HANDLE_MESSAGES];
long last_message_received;
String name;
String userName;
int longPoll = 0;
int waitForResponse = 5000; //jz = 1500;
int jzdelay = 10; //60; // delay between multipart blocks
int jzblocksize = 2 * 1024; // multipart block size
private:
// JsonObject * parseUpdates(String response);
String _token;
WiFiClientSecure *client; //Client *client; //a1 2.02 esp32-arduino
void closeClient();
const int maxMessageLength = 1500; //was 1500
bool processResult(JsonObject result, int messageIndex);
};
#endif

65
vA1/settings.h 100644
Wyświetl plik

@ -0,0 +1,65 @@
static const char devname[] = "desklens"; // name of your camera for mDNS, Router, and filenames
#define include_telegram
//#define include_pir_and_touch
#define include_ftp
#define include_streaming
#define get_rid_of_touch
int delete_old_files = 1; // set to 1 and it will delete your oldest day of files so you SD is always 10% empty
// https://sites.google.com/a/usapiens.com/opnode/time-zones -- find your timezone here
#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // your timezone - this is GMT
// 1 for blink red led with every sd card write, at your frame rate
// 0 for blink only for skipping frames and SOS if camera or sd is broken
#define BlinkWithWrite 1
// EDIT ssid and password **** with Version 98x-WiFiMan, you are using WiFiManager to set ssid and password, so these are redundant
const char* ssid = "jzjzjzjz";
const char* password = "mrpeanut";
// reboot startup parameters here
int Internet_Enabled = 1; // set to 0 to shut off all internet activities - wifi, time, http, ftp, telegram
int DeepSleepPir = 0; // set to 1 to deepsleep between pir videos
int record_on_reboot = 1; // set to 1 to record, or 0 to NOT record on reboot
int PIRpin = 13; // for active high pir or microwave etc
int PIRenabled = 0; // 1 is PIR is enable on reboot, will only work if you are not recording
int MagicNumber = 011; // change this if you are re-compiling and you dont want to use the ESPROM settings
int stream_interval = 333; // milliseconds between frames delivered during the live stream - 333 is 3 fps
// here are 2 sets of startup parameters -- more down in the stop and restart webpage
// VGA 10 fps for 30 minutes, and repeat, play at real time
int framesize = 8; // 13 UXGA, 11 HD, 9 SVGA, 8 VGA, 6 CIF
int repeat_config = 100; // repaeat same movie this many times
int xspeed = 1; // playback speed - realtime is 1, or 300 means playpack 30 fps of frames at 10 second per frame ( 30 fps / 0.1 fps )
int gray = 0; // not gray
int quality = 12; // quality on the 10..50 subscale - 10 is good, 20 is grainy and smaller files, 12 is better in bright sunshine due to clipping
int capture_interval = 100; // milli-seconds between frames
volatile int total_frames_config = 18000; // how many frames - length of movie in ms is total_frames x capture_interval
// UXGA 1 frame every 10 seconds for 60 minutes, and repeat, play at 30 fps or 300 times speed
/*
int framesize = 13; // 13 UXGA, 11 HD, 9 SVGA, 8 VGA, 6 CIF
int repeat_config = 300; // repaeat same movie this many times
int xspeed = 300; // playback speed - realtime is 1, or 300 means playpack 30 fps of frames at 10 second per frames ( 30 fps / 0.1 fps )
int gray = 0; // not gray
int quality = 6; // quality on the 10..50 subscale - 10 is good, 20 is grainy and smaller files, 12 is better in bright sunshine due to clipping
int capture_interval = 10000; // milli-seconds between frames
volatile int total_frames_config = 360; // how many frames - length of movie is total_frames x capture_interval
*/
// enable the www.telegram.org BOT - it sends a text and and snapshot to you every time it starts a video
// https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot
// I'm using the branch v1.2 from June 2020 - new master introduced late june, but not working for picture and captions, so my v1.2 mods included here
// You need to create a bot, and get its number BOTtoken, and then get your telegram number -- all free at telegram.org
// detailed instructions here https://randomnerdtutorials.com/telegram-control-esp32-esp8266-nodemcu-outputs/
RTC_DATA_ATTR int EnableBOT = 0;
#define BOTtoken "9876543210:qwertyuiopasdfghjklzxcvbnmqwertyuio" // get your own bot and id at telegram.org
#define BOTme "1234567890"