Upload files to "/"
This commit is contained in:
parent
94f2c99da4
commit
7ed3e02fc5
|
|
@ -0,0 +1,368 @@
|
||||||
|
#include "FileLib.h"
|
||||||
|
|
||||||
|
FileLibClass::FileLibClass(StorageType type) : _type(type) {}
|
||||||
|
|
||||||
|
FileLibClass::~FileLibClass()
|
||||||
|
{
|
||||||
|
_printMessage("Unmounting file system\n");
|
||||||
|
if (_type == StorageType::FL_SPIFFS)
|
||||||
|
{
|
||||||
|
LITTLEFS.end();
|
||||||
|
}
|
||||||
|
else if (_type == StorageType::FL_MMC)
|
||||||
|
{
|
||||||
|
SD_MMC.end();
|
||||||
|
}
|
||||||
|
else if (_type == StorageType::FL_SD)
|
||||||
|
{
|
||||||
|
SD.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::begin(uint8_t csPin, uint8_t misoPin, uint8_t mosiPin, uint8_t sckPin)
|
||||||
|
{
|
||||||
|
_printMessage("Initializing file system\n");
|
||||||
|
if (_type == StorageType::FL_SPIFFS)
|
||||||
|
{
|
||||||
|
_filesystem = &LITTLEFS;
|
||||||
|
return LITTLEFS.begin(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type == StorageType::FL_MMC)
|
||||||
|
{
|
||||||
|
_filesystem = &SD_MMC;
|
||||||
|
return SD_MMC.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type == StorageType::FL_SD)
|
||||||
|
{
|
||||||
|
_spi.begin(sckPin, misoPin, mosiPin, csPin);
|
||||||
|
_filesystem = &SD;
|
||||||
|
return SD.begin(5, _spi);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File FileLibClass::openForWriting(const char *fileName)
|
||||||
|
{
|
||||||
|
_printMessage("Opening file '%s' for writing\n", fileName);
|
||||||
|
return _filesystem->open(fileName, FILE_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
File FileLibClass::open(const char *fileName, const char *mode)
|
||||||
|
{
|
||||||
|
if (!_filesystem)
|
||||||
|
{
|
||||||
|
_printMessage("File system not initialized");
|
||||||
|
return File(); // Return an empty File object if filesystem is not initialized
|
||||||
|
}
|
||||||
|
return _filesystem->open(fileName, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::readPartial(const char *fileName, uint32_t startPos, uint8_t *buffer, uint16_t bufferSize)
|
||||||
|
{
|
||||||
|
if (!_filesystem)
|
||||||
|
{
|
||||||
|
_printMessage("File system not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = _filesystem->open(fileName, FILE_READ);
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
_printMessage("Failed to open file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.seek(startPos))
|
||||||
|
{
|
||||||
|
_printMessage("Failed to seek to position %u in file", startPos);
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytesRead = file.read(buffer, bufferSize);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (bytesRead == 0)
|
||||||
|
{
|
||||||
|
_printMessage("No data read from file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::write(const char *fileName, const uint8_t *data, size_t dataSize, bool append)
|
||||||
|
{
|
||||||
|
_printMessage("Writing %d bytes to file '%s'\n", dataSize, fileName);
|
||||||
|
File file = _filesystem->open(fileName, append ? FILE_APPEND : FILE_WRITE);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (!file || file.isDirectory())
|
||||||
|
{
|
||||||
|
_printMessage("Failed to open file '%s' for writing\n", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// size_t written = file.write(reinterpret_cast<const uint8_t *>(data), dataSize);
|
||||||
|
size_t written = 0;
|
||||||
|
for (size_t i = 0; i < dataSize; i++)
|
||||||
|
{
|
||||||
|
written += file.write(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm that the data was written
|
||||||
|
if (written == dataSize)
|
||||||
|
{
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (written > 0)
|
||||||
|
{
|
||||||
|
_printMessage("Only wrote %d bytes of %d\n", written, dataSize);
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_printMessage("Failed to write to file '%s'\n", fileName);
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::writeString(const char *fileName, const uint8_t *data, bool append)
|
||||||
|
{
|
||||||
|
_printMessage("Writing string to file '%s'\n", fileName);
|
||||||
|
// use strlen so we don't write the null terminator
|
||||||
|
int strLen = strlen((char *)&data);
|
||||||
|
return write(fileName, reinterpret_cast<const uint8_t *>(data), strLen, append);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLibClass::listDir(const char *dirName, uint8_t levels)
|
||||||
|
{
|
||||||
|
if (!_verbose)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_printMessage("Listing directory: %s\r\n", dirName);
|
||||||
|
|
||||||
|
File root = _filesystem->open(dirName);
|
||||||
|
if (!root)
|
||||||
|
{
|
||||||
|
_printMessage("− failed to open directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.isDirectory())
|
||||||
|
{
|
||||||
|
_printMessage(" − not a directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = root.openNextFile();
|
||||||
|
while (file)
|
||||||
|
{
|
||||||
|
if (file.isDirectory())
|
||||||
|
{
|
||||||
|
Serial.printf(" DIR : %s\n", file.name());
|
||||||
|
if (levels)
|
||||||
|
{
|
||||||
|
listDir(file.name(), levels - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.printf(" FILE: %s", file.name());
|
||||||
|
Serial.printf("\tSIZE: %d\n", file.size());
|
||||||
|
}
|
||||||
|
file = root.openNextFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::read(const char *fileName, uint8_t *buffer, uint16_t bufferSize, bool clearBuffer)
|
||||||
|
{
|
||||||
|
_printMessage("Reading file '%s'\n", fileName);
|
||||||
|
if (clearBuffer)
|
||||||
|
{
|
||||||
|
memset(buffer, 0, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = _filesystem->open(fileName, FILE_READ);
|
||||||
|
|
||||||
|
uint16_t fileSize = file.size();
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (!file || file.isDirectory())
|
||||||
|
{
|
||||||
|
_printMessage("Failed to open file '%s'.\n", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the buffer is big enough
|
||||||
|
if (bufferSize < fileSize)
|
||||||
|
{
|
||||||
|
_printMessage("Buffer size is too small for file '%s'.\n", fileName);
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.available())
|
||||||
|
{
|
||||||
|
_printMessage("File '%s' is empty.\n", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// size_t bytesRead = file.readBytes(buffer, fileSize);
|
||||||
|
// read the data byte by byte
|
||||||
|
for (size_t i = 0; i < fileSize; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = file.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::remove(const char *fileName)
|
||||||
|
{
|
||||||
|
_printMessage("Removing file '%s'\n", fileName);
|
||||||
|
if (!_filesystem->exists(fileName))
|
||||||
|
{
|
||||||
|
_printMessage("File '%s' does not exist.\n", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _filesystem->remove(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::rename(const char *fileName, const char *newName, bool overwrite)
|
||||||
|
{
|
||||||
|
_printMessage("Renaming file '%s' to '%s'\n", fileName, newName);
|
||||||
|
if (_filesystem->exists(newName) && !overwrite)
|
||||||
|
{
|
||||||
|
_printMessage("File '%s' already exists.\n", newName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _filesystem->rename(fileName, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileLibClass::mkdir(const char *path)
|
||||||
|
{
|
||||||
|
_printMessage("Creating directory '%s'\n", path);
|
||||||
|
|
||||||
|
if (_filesystem->exists(path))
|
||||||
|
{
|
||||||
|
_printMessage("Directory '%s' already exists.\n", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _filesystem->mkdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t FileLibClass::getFreeSpace()
|
||||||
|
{
|
||||||
|
_printMessage("Getting free space\n");
|
||||||
|
if (_type == StorageType::FL_SPIFFS)
|
||||||
|
{
|
||||||
|
return LITTLEFS.totalBytes() - LITTLEFS.usedBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type == StorageType::FL_MMC)
|
||||||
|
{
|
||||||
|
uint64_t cardSize = SD_MMC.totalBytes();
|
||||||
|
uint64_t usedSpace = SD_MMC.usedBytes();
|
||||||
|
|
||||||
|
if (usedSpace > cardSize)
|
||||||
|
{
|
||||||
|
_printMessage("ERROR: Used space is greater than card size \n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t freeSpace = cardSize - usedSpace;
|
||||||
|
return freeSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_type == StorageType::FL_SD)
|
||||||
|
{
|
||||||
|
uint64_t cardSize = SD.totalBytes();
|
||||||
|
uint64_t usedSpace = SD.usedBytes();
|
||||||
|
|
||||||
|
if (usedSpace > cardSize)
|
||||||
|
{
|
||||||
|
_printMessage("Error: Used space is greater than card size \n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t freeSpace = cardSize - usedSpace;
|
||||||
|
return freeSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copy(File &source, File &dest)
|
||||||
|
{
|
||||||
|
if (!source || !dest)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bufferSize = 512;
|
||||||
|
char buffer[bufferSize];
|
||||||
|
|
||||||
|
while (source.available())
|
||||||
|
{
|
||||||
|
int bytesRead = source.readBytes(buffer, bufferSize);
|
||||||
|
int bytesWritten = dest.write((uint8_t *)&buffer, bytesRead);
|
||||||
|
|
||||||
|
if (bytesWritten != bytesRead)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.flush();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLibClass::_printMessage(const char *message, ...)
|
||||||
|
{
|
||||||
|
if (_verbose)
|
||||||
|
{
|
||||||
|
// get the type name
|
||||||
|
const char *typeName;
|
||||||
|
switch (_type)
|
||||||
|
{
|
||||||
|
case FL_SPIFFS:
|
||||||
|
typeName = "FL_SPIFFS";
|
||||||
|
break;
|
||||||
|
case FL_SD:
|
||||||
|
typeName = "FL_SD";
|
||||||
|
break;
|
||||||
|
case FL_MMC:
|
||||||
|
typeName = "FL_MMC";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
typeName = "Unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[256];
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, message);
|
||||||
|
vsnprintf(buffer, sizeof(buffer), message, args);
|
||||||
|
va_end(args);
|
||||||
|
// switch for different storage types
|
||||||
|
Serial.printf("%s: %s", typeName, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create FileLibClass::exists function
|
||||||
|
bool FileLibClass::exists(const char* fileName)
|
||||||
|
{
|
||||||
|
return _filesystem->exists(fileName);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef GENERIC_TYPE_DEFS_H
|
||||||
|
#define GENERIC_TYPE_DEFS_H
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
typedef unsigned long dword;
|
||||||
|
typedef unsigned long uint32;
|
||||||
|
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
uint32 val;
|
||||||
|
word w[2];
|
||||||
|
byte v[4];
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
word lw;
|
||||||
|
word hw;
|
||||||
|
} wd;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
byte lb;
|
||||||
|
byte hb;
|
||||||
|
byte ub;
|
||||||
|
byte mb;
|
||||||
|
} bt;
|
||||||
|
} DWORD_VAL;
|
||||||
|
|
||||||
|
#endif // GENERIC_TYPE_DEFS_H
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
#ifndef HTTPS_HANDLER_H
|
||||||
|
#define HTTPS_HANDLER_H
|
||||||
|
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
#include "SD_MMC.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class HTTPSHandler
|
||||||
|
*
|
||||||
|
* This class provides functionality to handle HTTPS connections, including downloading,
|
||||||
|
* uploading, and sending post requests. It uses a state machine internally to track the
|
||||||
|
* status of downloads.
|
||||||
|
*/
|
||||||
|
class HTTPSHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @enum HttpsState
|
||||||
|
*
|
||||||
|
* Enumerates the possible states for the https class.
|
||||||
|
*/
|
||||||
|
enum class HttpsState
|
||||||
|
{
|
||||||
|
IDLE, // Initial state, no download in progress.
|
||||||
|
CONNECTING, // Connecting to the server.
|
||||||
|
WAITING, // Waiting for server response.
|
||||||
|
COMPLETE, // Response is complete.
|
||||||
|
ERROR // An error occurred during the download.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum Type of http request
|
||||||
|
*/
|
||||||
|
enum class RequestType
|
||||||
|
{
|
||||||
|
GET,
|
||||||
|
DOWNLOAD,
|
||||||
|
POST,
|
||||||
|
UPLOAD
|
||||||
|
};
|
||||||
|
|
||||||
|
// A callback type that notifies about download progress.
|
||||||
|
using ProgressCallback = void (*)(int bytesDownloaded, int totalBytes);
|
||||||
|
|
||||||
|
private:
|
||||||
|
WiFiClientSecure _client;
|
||||||
|
// The server's certificate for SSL/TLS connection. Typically the root CA certificate.
|
||||||
|
const char *_serverCert;
|
||||||
|
// Must be a domain name, not an IP address.
|
||||||
|
const char *_host;
|
||||||
|
// Range between 0 and 65535.
|
||||||
|
int _port = 443;
|
||||||
|
|
||||||
|
// Duration in seconds.
|
||||||
|
uint8_t _downloadTimeout = 90;
|
||||||
|
// Duration in seconds.
|
||||||
|
uint8_t _responseTimeout = 100;
|
||||||
|
|
||||||
|
// Callback function to get download progress updates.
|
||||||
|
ProgressCallback _progressCallback = nullptr;
|
||||||
|
// Current state of the download.
|
||||||
|
HttpsState _currentState = HttpsState::IDLE;
|
||||||
|
// Type of http request.
|
||||||
|
RequestType _requestType;
|
||||||
|
// Boundaries for upload.
|
||||||
|
const String _BOUNDARY = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
|
||||||
|
|
||||||
|
String _response;
|
||||||
|
|
||||||
|
// The File object where the downloaded data will be saved.
|
||||||
|
File _destinationFile;
|
||||||
|
// The path or endpoint on the server.
|
||||||
|
const char *_path;
|
||||||
|
// A header to be sent with the request.
|
||||||
|
String _customHeader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish a connection to the server.
|
||||||
|
* @return true if connected successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
bool _connect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method to get the content length from the server's response headers.
|
||||||
|
* @return The content length as an integer.
|
||||||
|
*/
|
||||||
|
int _getContentLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the server's response for a given request.
|
||||||
|
* This method reads the available data from the client, appends it to
|
||||||
|
* the internal response string, and updates the current state
|
||||||
|
* of the HTTPSHandler based on the contents of the response.
|
||||||
|
*/
|
||||||
|
void _processResponse();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the download once initiated.
|
||||||
|
* @param dest The File object where the downloaded data will be saved.
|
||||||
|
*/
|
||||||
|
void _processDownload(File &dest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get only the file name from a given path.
|
||||||
|
*/
|
||||||
|
String _extractFileName(const String &fullPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create boundary for multipart upload.
|
||||||
|
* Used in http request header.
|
||||||
|
*/
|
||||||
|
String _createBoundary(const String &fileNameToUpload);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructor for the HTTPSHandler class.
|
||||||
|
* @param host The server's domain or IP address.
|
||||||
|
* @param port The port to connect on the server.
|
||||||
|
* @param serverCert The server's certificate for SSL/TLS connection.
|
||||||
|
*/
|
||||||
|
HTTPSHandler(const char *serverCert);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor for the HTTPSHandler class.
|
||||||
|
*/
|
||||||
|
~HTTPSHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish a connection to the server.
|
||||||
|
* @param host The server host to connect to.
|
||||||
|
* @param port The server port to connect to. Defaults to 443.
|
||||||
|
* @param serverCert The server certificate. Defaults to nullptr.
|
||||||
|
* @return true if connected successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
bool connect(const char *host, int port = 443, const char *serverCert = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the server's response for the most recent request.
|
||||||
|
*
|
||||||
|
* @return A string containing the full response from the server.
|
||||||
|
*/
|
||||||
|
String getResponse() { return _response; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the server's response for the most recent request is complete.
|
||||||
|
*
|
||||||
|
* @return True if the response is complete, otherwise false.
|
||||||
|
*/
|
||||||
|
bool isResponseComplete() { return _currentState == HttpsState::COMPLETE; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a callback function to get download progress updates.
|
||||||
|
* @param callback A function pointer to the callback.
|
||||||
|
*/
|
||||||
|
void setProgressCallback(ProgressCallback callback) { _progressCallback = callback; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current download state.
|
||||||
|
* @return The current state of the download.
|
||||||
|
*/
|
||||||
|
HttpsState getState() const { return _currentState; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a custom header for the next HTTP request.
|
||||||
|
* @param header The header string to set.
|
||||||
|
*/
|
||||||
|
void setCustomHeader(const String &header) { _customHeader = header; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate the download process.
|
||||||
|
* @param path The path or endpoint on the server from where to download.
|
||||||
|
*/
|
||||||
|
void getRequest(const char *path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload a file to the server.
|
||||||
|
* @param path The path or endpoint on the server where to upload.
|
||||||
|
* @param source The File object containing the data to be uploaded.
|
||||||
|
* @param fileName The name that will be used for the uploaded file on the server.
|
||||||
|
* If not specified, the name of the source file will be used.
|
||||||
|
* @return true if the upload was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
bool uploadFile(const char *path, File &source, const String &fileName = "");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a POST request to the server.
|
||||||
|
* @param path The path or endpoint on the server to send the POST request.
|
||||||
|
* @param postData The data to be sent in the POST request.
|
||||||
|
* @return true if the request was successful, false otherwise.
|
||||||
|
*/
|
||||||
|
bool postRequest(const char *path, const String &postData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the target from a given path and destination file.
|
||||||
|
* @param path The path or endpoint on the server from where to download.
|
||||||
|
* @param dest The File object where the downloaded data will be saved.
|
||||||
|
*/
|
||||||
|
void setTarget(const char *sourcePath, File &dest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue processing the state machine.
|
||||||
|
*/
|
||||||
|
void advanceStateMachine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error occurred during the download.
|
||||||
|
* @return true if an error occurred, false otherwise.
|
||||||
|
*/
|
||||||
|
bool isErrorOccurred() { return _currentState == HttpsState::ERROR; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the current connection with the server.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// void get(const String &url, Callback callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPS_HANDLER_H
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
#include "HTTPSHandler.h"
|
||||||
|
|
||||||
|
HTTPSHandler::HTTPSHandler(const char *serverCert)
|
||||||
|
{
|
||||||
|
_client.setCACert(serverCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPSHandler::connect(const char *host, int port, const char *serverCert)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
_port = port;
|
||||||
|
if (serverCert != nullptr)
|
||||||
|
{
|
||||||
|
_client.setCACert(serverCert);
|
||||||
|
}
|
||||||
|
_currentState = HttpsState::CONNECTING;
|
||||||
|
// reset response
|
||||||
|
_response = "";
|
||||||
|
return _connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPSHandler::_connect()
|
||||||
|
{
|
||||||
|
return _client.connect(_host, _port);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPSHandler::_getContentLength()
|
||||||
|
{
|
||||||
|
unsigned long startTime = millis();
|
||||||
|
|
||||||
|
String line;
|
||||||
|
int contentLength = -1;
|
||||||
|
|
||||||
|
if (!_client.connected())
|
||||||
|
{
|
||||||
|
Serial.print("Not connected when getting content length");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (contentLength == -1)
|
||||||
|
{
|
||||||
|
while (_client.available())
|
||||||
|
{
|
||||||
|
line = _client.readStringUntil('\n');
|
||||||
|
line.trim(); // Remove any trailing whitespace
|
||||||
|
|
||||||
|
if (line.startsWith("Content-Length: "))
|
||||||
|
{
|
||||||
|
contentLength = line.substring(15).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty line / end of headers
|
||||||
|
if (line.length() == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for timeout
|
||||||
|
if (millis() - startTime > _responseTimeout * 1000)
|
||||||
|
{
|
||||||
|
Serial.println("Timeout while trying to get Content-Length!");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPSHandler::getRequest(const char *path)
|
||||||
|
{
|
||||||
|
// Check if the client is connected before sending
|
||||||
|
if (!_client.connected())
|
||||||
|
{
|
||||||
|
Serial.println("Error: Client is not connected!");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String request = "GET " + String(path) + " HTTP/1.1\r\n";
|
||||||
|
request += "Host: " + String(_host) + "\r\n";
|
||||||
|
|
||||||
|
if (_customHeader.length() > 0)
|
||||||
|
{
|
||||||
|
request += _customHeader + "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
request += "Connection: keep-alive\r\n\r\n";
|
||||||
|
|
||||||
|
size_t bytesSent = _client.print(request);
|
||||||
|
|
||||||
|
// Check if all bytes of the request were sent
|
||||||
|
if (bytesSent != request.length())
|
||||||
|
{
|
||||||
|
Serial.println("Error: Not all bytes sent!");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentState = HttpsState::WAITING;
|
||||||
|
_requestType = RequestType::GET;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPSHandler::_processDownload(File &dest)
|
||||||
|
{
|
||||||
|
const uint32_t BUFFER_SIZE = 2048;
|
||||||
|
int contentLength = _getContentLength();
|
||||||
|
static int bytesRead = 0; // Number of bytes downloaded so far
|
||||||
|
byte buffer[BUFFER_SIZE];
|
||||||
|
unsigned long startMillis = millis();
|
||||||
|
|
||||||
|
if (contentLength <= 0)
|
||||||
|
{
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_currentState == HttpsState::WAITING)
|
||||||
|
{
|
||||||
|
// Then proceed to download content
|
||||||
|
while (_client.available())
|
||||||
|
{
|
||||||
|
// If no data is received for the duration of _timeout, it times out
|
||||||
|
if (millis() - startMillis > _downloadTimeout * 1000)
|
||||||
|
{
|
||||||
|
Serial.println("Data inactivity timeout!");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
dest.close();
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t bytesAvailable = _client.available();
|
||||||
|
int bytesToRead = min(bytesAvailable, BUFFER_SIZE);
|
||||||
|
|
||||||
|
int bytesReadFromClient = _client.read(buffer, bytesToRead);
|
||||||
|
dest.write(buffer, bytesReadFromClient);
|
||||||
|
bytesRead += bytesReadFromClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_progressCallback)
|
||||||
|
{
|
||||||
|
_progressCallback(bytesRead, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesRead >= contentLength)
|
||||||
|
{
|
||||||
|
dest.close();
|
||||||
|
_currentState = HttpsState::COMPLETE;
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPSHandler::_extractFileName(const String &fullPath)
|
||||||
|
{
|
||||||
|
int lastSlashIndex = fullPath.lastIndexOf('/');
|
||||||
|
|
||||||
|
if (lastSlashIndex == -1)
|
||||||
|
{
|
||||||
|
// No slashes found, return the full path itself.
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the substring from the last slash onwards.
|
||||||
|
return fullPath.substring(lastSlashIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPSHandler::_createBoundary(const String &fileNameToUpload)
|
||||||
|
{
|
||||||
|
// Build the multipart form data request
|
||||||
|
String boundary = "--" + _BOUNDARY + "\r\n";
|
||||||
|
boundary += "Content-Disposition: form-data; name=\"fileToUpload\"; filename=\"" + fileNameToUpload + "\"\r\n";
|
||||||
|
boundary += "Content-Type: application/json\r\n";
|
||||||
|
boundary += "\r\n";
|
||||||
|
return boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPSHandler::uploadFile(const char *destPath, File &source, const String &fileName)
|
||||||
|
{
|
||||||
|
String fileNameToUpload = fileName == "" ? _extractFileName(source.name()) : fileName;
|
||||||
|
Serial.println("Uploading file: " + fileNameToUpload);
|
||||||
|
|
||||||
|
// Needed for header
|
||||||
|
String boundary = _createBoundary(fileNameToUpload);
|
||||||
|
String endBoundary = "\r\n--" + _BOUNDARY + "--\r\n";
|
||||||
|
|
||||||
|
// Calculate content length
|
||||||
|
int contentLength = boundary.length() + endBoundary.length() + source.size();
|
||||||
|
|
||||||
|
// Start sending the request
|
||||||
|
_client.println("POST " + String(destPath) + " HTTP/1.1");
|
||||||
|
_client.println("Host: appdig.com");
|
||||||
|
if (_customHeader.length() > 0)
|
||||||
|
{
|
||||||
|
_client.println(_customHeader);
|
||||||
|
}
|
||||||
|
_client.print("Content-Length: " + String(contentLength) + "\r\n");
|
||||||
|
_client.println("Content-Type: multipart/form-data; boundary=" + _BOUNDARY);
|
||||||
|
_client.println();
|
||||||
|
_client.print(boundary);
|
||||||
|
|
||||||
|
const int CHUNK_SIZE = 1024;
|
||||||
|
const long startTime = millis();
|
||||||
|
// Start sending body in chunks
|
||||||
|
while (source.available())
|
||||||
|
{
|
||||||
|
// Check for timeout
|
||||||
|
if (millis() - startTime > _responseTimeout * 1000)
|
||||||
|
{
|
||||||
|
Serial.println("Timeout while trying to get Content-Length!");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
byte buffer[CHUNK_SIZE];
|
||||||
|
int bytesRead = source.read(buffer, CHUNK_SIZE);
|
||||||
|
_client.write(buffer, bytesRead);
|
||||||
|
|
||||||
|
if (_progressCallback)
|
||||||
|
{
|
||||||
|
_progressCallback(bytesRead, contentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.print(endBoundary);
|
||||||
|
|
||||||
|
_currentState = HttpsState::COMPLETE;
|
||||||
|
// give the server some time to process the request
|
||||||
|
// update in the future to handle response from server
|
||||||
|
delay(10);
|
||||||
|
close();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPSHandler::postRequest(const char *destPath, const String &postData)
|
||||||
|
{
|
||||||
|
_client.println("POST " + String(destPath) + " HTTP/1.1");
|
||||||
|
_client.println("Host: " + String(_host));
|
||||||
|
_client.println("Content-Length: " + String(postData.length()));
|
||||||
|
_client.println("Connection: close");
|
||||||
|
_client.println();
|
||||||
|
_client.println(postData);
|
||||||
|
|
||||||
|
_currentState = HttpsState::WAITING;
|
||||||
|
_requestType = RequestType::POST;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPSHandler::_processResponse()
|
||||||
|
{
|
||||||
|
unsigned long startMillis = millis();
|
||||||
|
// Read available data from the client and append to _response
|
||||||
|
while (_client.available())
|
||||||
|
{
|
||||||
|
// If no data is received for the duration of _timeout, it times out
|
||||||
|
if (millis() - startMillis > _responseTimeout * 1000)
|
||||||
|
{
|
||||||
|
Serial.println("Data inactivity timeout");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = _client.read();
|
||||||
|
Serial.print(c);
|
||||||
|
_response += c;
|
||||||
|
|
||||||
|
if (_response.endsWith("\r\n0\r\n"))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_client.connected() || _response.endsWith("\r\n0\r\n"))
|
||||||
|
{
|
||||||
|
_currentState = HttpsState::COMPLETE;
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPSHandler::setTarget(const char *sourcePath, File &destFile)
|
||||||
|
{
|
||||||
|
_path = sourcePath;
|
||||||
|
_destinationFile = destFile;
|
||||||
|
_currentState = HttpsState::WAITING;
|
||||||
|
getRequest(_path);
|
||||||
|
_requestType = RequestType::DOWNLOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPSHandler::advanceStateMachine()
|
||||||
|
{
|
||||||
|
static int lastState = -1;
|
||||||
|
if (lastState != static_cast<int>(_currentState))
|
||||||
|
{
|
||||||
|
Serial.println("HTTPSHandler state changed to " + static_cast<int>(_currentState));
|
||||||
|
lastState = static_cast<int>(_currentState);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_currentState)
|
||||||
|
{
|
||||||
|
case HttpsState::CONNECTING:
|
||||||
|
if (!_client.connected())
|
||||||
|
{
|
||||||
|
Serial.println("Could not connect to server");
|
||||||
|
_currentState = HttpsState::ERROR;
|
||||||
|
}
|
||||||
|
_currentState = HttpsState::WAITING;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpsState::WAITING:
|
||||||
|
switch (_requestType)
|
||||||
|
{
|
||||||
|
case RequestType::DOWNLOAD:
|
||||||
|
_processDownload(_destinationFile);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
_processResponse();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpsState::ERROR:
|
||||||
|
_currentState = HttpsState::IDLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpsState::COMPLETE:
|
||||||
|
_currentState = HttpsState::IDLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HttpsState::IDLE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void HTTPSHandler::get(const String &url, Callback callback)
|
||||||
|
// {
|
||||||
|
// if (!_client.connect(_host, _port))
|
||||||
|
// {
|
||||||
|
// Serial.println("Could not connect to server");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _client.println("GET / HTTP/1.1");
|
||||||
|
// _client.println("HOST appdig.com");
|
||||||
|
// _client.println("Connection: close");
|
||||||
|
|
||||||
|
// _requestStarted = 1;
|
||||||
|
// _callback = callback;
|
||||||
|
// }
|
||||||
|
|
||||||
|
void HTTPSHandler::close()
|
||||||
|
{
|
||||||
|
if (_client.connected())
|
||||||
|
{
|
||||||
|
_client.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPSHandler::~HTTPSHandler()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
#ifndef IO_ADI
|
||||||
|
|
||||||
|
#define IO_ADI 1
|
||||||
|
|
||||||
|
#define IOSIZE 40
|
||||||
|
uint16_t io_array[IOSIZE];
|
||||||
|
|
||||||
|
#define BATTERYINPUTPIN 32 // 26 v996
|
||||||
|
#define SPAREOUTPIN 26 // 32 v996
|
||||||
|
// #define ADDR_PB 17
|
||||||
|
|
||||||
|
enum io
|
||||||
|
{
|
||||||
|
// Inputs
|
||||||
|
RET_LIMIT, // JOG_EXTEND, //0
|
||||||
|
EXT_LIMIT, // JOG_RETRACT,
|
||||||
|
OPEN_LIMIT,
|
||||||
|
ADDRBUTTON, // SPARE_IN2,
|
||||||
|
COMPRESSOR, // SPARE_IN3,
|
||||||
|
EN_12V_SW, // RF_KEYPAD,
|
||||||
|
RF_KEYPAD, // SPARE_IN1,
|
||||||
|
POWER_RLY, // ADDR_SET,//7
|
||||||
|
|
||||||
|
SPARE_IN, // GPIO 25
|
||||||
|
SPARE_OUT, // GPIO 32
|
||||||
|
BATT_V // GPIO 26
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
//outputs
|
||||||
|
RETRACT,//8
|
||||||
|
EXTEND,
|
||||||
|
SPARE_RLYA, //was SCRN_DN
|
||||||
|
SPARE_RLYB, //was SCRN_UP
|
||||||
|
SPARE_OUT1,
|
||||||
|
EN_SWA,
|
||||||
|
EN_SWB,
|
||||||
|
EN_SWC,//15
|
||||||
|
|
||||||
|
COMPRESSOR,//16
|
||||||
|
ADDR_LED,
|
||||||
|
HI_TRIP_LED,
|
||||||
|
CHARGE_EN,
|
||||||
|
CHARGE_MODE,
|
||||||
|
SCREEN_UP,
|
||||||
|
SCREEN_DN,
|
||||||
|
SCREEN_STP,//23
|
||||||
|
|
||||||
|
spare1,//24
|
||||||
|
spare2,//25
|
||||||
|
spare3,//26
|
||||||
|
|
||||||
|
//ADC
|
||||||
|
BATT,//27
|
||||||
|
GND,
|
||||||
|
ADC4to20,
|
||||||
|
ADC0to10a,
|
||||||
|
ADC_CURRENT,
|
||||||
|
JOG_RETRACT,
|
||||||
|
JOG_EXTEND,
|
||||||
|
ADPAREADC3 //34
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
// io Expander Support v125
|
||||||
|
|
||||||
|
// set up for 2 modules
|
||||||
|
uint8_t exRly[4];
|
||||||
|
uint8_t exOpto[4];
|
||||||
|
uint8_t exOptoTransition[4];
|
||||||
|
uint16_t exParm[12]; // 0-3 are input Hz 4-11 are 4 to 20 AD
|
||||||
|
|
||||||
|
uint8_t exIndx;
|
||||||
|
uint8_t exParmIndx;
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue