I try to upload files from SD card of esp32cam module, it works but not quite as it tries to upload files to root folder on FTP server instead of working directory. Can you help me to figure out where to problem is in my code?:
#include <WiFi.h>
#include <ESP32_FTPClient.h>
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include <SPI.h>
#include <Arduino.h>
// Replace with your network credentials
const char* ssid = "wifi";
const char* password = "password";
// FTP server credentials
const char* ftp_server = "192.168.0.20";
const char* ftp_user = "user";
const char* ftp_pass = "passwd";
// FTP client instance
ESP32_FTPClient ftp((char*)ftp_server, (char*)ftp_user, (char*)ftp_pass);
// Initialize the micro SD card
void initMicroSDCard(){
Serial.println("Starting SD Card");
if(!SD_MMC.begin("/sdcard", true)){
Serial.println("SD Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
}
void listAndUploadFiles(fs::FS &fs, const char* dirname);
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Initialize MicroSD
Serial.print("Initializing the MicroSD card module... ");
initMicroSDCard();
// Initialize FTP connection
ftp.OpenConnection();
//list files on SD card
listAndUploadFiles(SD_MMC, "/");
}
void loop() {
// Do nothing here
}
void uploadFile(const char* path) {
File file = SD_MMC.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
ftp.OpenConnection();
ftp.ChangeWorkDir("/media/share/esp32cam1"); // Change to the desired directory on FTP server
ftp.InitFile("Type I");
ftp.NewFile(path);
const size_t bufferSize = 512;
uint8_t buffer[bufferSize];
while (file.available()) {
size_t bytesRead = file.read(buffer, bufferSize);
ftp.WriteData(buffer, bytesRead);
}
ftp.CloseFile();
ftp.CloseConnection();
file.close();
Serial.println("File uploaded successfully");
}
void listAndUploadFiles(fs::FS &fs, const char* dirname) {
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
listAndUploadFiles(fs, file.name()); // Recursively list files in subdirectories
} else {
Serial.print("Uploading file: ");
Serial.println(file.name());
String filePath = String(file.name());
if (!filePath.startsWith("/")) {
filePath = "/" + filePath;
}
uploadFile(filePath.c_str());
}
file = root.openNextFile();
}
}
Above code seems make esp32 to connect to FTP and attempt to copy files but I get following error on the FTP server:
Thu Jun 6 04:31:46 2024 [pid 320341] CONNECT: Client "::ffff:192.168.0.228"
Thu Jun 6 04:31:46 2024 [pid 320340] [user] OK LOGIN: Client "::ffff:192.168.0.228"
Thu Jun 6 04:31:46 2024 [pid 320342] [user] FAIL UPLOAD: Client "::ffff:192.168.0.228", "/picture_2024-06-05_02-52-44.jpg", 0.00Kbyte/sec
Obviously I want ro use ftp.ChangeWorkDir("/media/share/esp32cam1"); code to be applied when choosing the FTP server directory destination.
I have ZERO knowledge of C++ so its kind of miracle I got it here putting together some random code that I found on the Internet, but this little success motivates me to learn more.
Any help appreciated!
As far as I can see, you should not include a "/" to the file name, because it means you're specifying the root "/" as target directory instead of the current one. It's clearly shown in the FAIL_UPLOAD:
@docdoc thank you that helped, but there is another problem now when uploading files to FTP, uploaded file is 0 size on the FTP server.
serial console shows:
[00:22:56][232767][E][vfs_api.cpp:29] open(): picture_2024-06-05_04-06-31.jpg does not start with /
[00:22:56]Failed to open file for reading
[00:22:56]ploading file
[00:22:56][233290][E][vfs_api.cpp:29] open(): picture_2024-06-05_04-06-42.jpg does not start with /
[00:22:56]Failed to open file for reading
FTP server shows that file is uploaded, however only creates an empty file:
Mon Jun 10 00:25:36 2024 [pid 61257] CONNECT: Client "::ffff:192.168.0.228"
Mon Jun 10 00:25:36 2024 [pid 61256] [vito] OK LOGIN: Client "::ffff:192.168.0.228"
Mon Jun 10 00:25:36 2024 [pid 61255] [vito] OK UPLOAD: Client "::ffff:192.168.0.228", "/media/share/alarms/esp32cam1/picture_2024-06-05_04-06-31.jpg", 0.00Kbyte/sec
Mon Jun 10 00:25:36 2024 [pid 61260] CONNECT: Client "::ffff:192.168.0.228"
Mon Jun 10 00:25:36 2024 [pid 61259] [vito] OK LOGIN: Client "::ffff:192.168.0.228"
Mon Jun 10 00:25:37 2024 [pid 61258] [vito] OK UPLOAD: Client "::ffff:192.168.0.228", "/media/share/alarms/esp32cam1/picture_2024-06-05_04-06-42.jpg", 0.00Kbyte/sec
EDIT: looks like figured it out!
Tried to modify my code and add "/" to SD_MMC.open section and it works now! Does not complain about file not starting with "/" anymore:
File file = SD_MMC.open("/" + String(path));
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
The SD needs the "/" for file path because it doesn't have a "current directory" like FTP.
But I think you still need to check your "listAndUploadFiles()" function a bit.
I don't know your current full code, but those lines include unnecessary operations:
With the first line you convert the "file.name" C string into "String", then you pass to uploadFile() function the converted "String" back into C string...
I think you can just do this:
uploadFile(file.name());
Then, the "listAndUploadFiles()" function is a recursive one, so if you force it to add "/" I think it prevents the function to correctly explore all SD card subdirectories, it'll work just for root files. I suppose your files are always there and you don't have any other subdir, in this case you can (should) remove the recursive call changing this:
if (file.isDirectory()) {
listAndUploadFiles(fs, file.name()); // Recursively list files in subdirectories
} else {
Serial.print("Uploading file: ");
into this:
if (!file.isDirectory()) {
Serial.print("Uploading file: ");
If in future you'll need recursive upload, you should change a bit those two functions (e.g. by avoiding that added "/", removing it instead from "uploadFile()" parameter "path", but to make things more flexible I'd also add one more parameter to that function to specify the FTP upload directory).
#include <WiFi.h>
#include <ESP32_FTPClient.h>
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include <SPI.h>
#include <Arduino.h>
// Replace with your network credentials
const char* ssid = "ssid";
const char* password = "password";
// FTP server credentials
const char* ftp_server = "192.168.0.20";
const char* ftp_user = "vito";
const char* ftp_pass = "passwrd";
const char* working_dir = "/media/share/alarms/esp32cam1/"; // Change to the desired directory on FTP server
// FTP client instance
ESP32_FTPClient ftp((char*)ftp_server, (char*)ftp_user, (char*)ftp_pass);
// Initialize the micro SD card
void initMicroSDCard(){
Serial.println("Starting SD Card");
if(!SD_MMC.begin("/sdcard", true)){
Serial.println("SD Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD Card attached");
return;
}
}
void listAndUploadFiles(fs::FS &fs, const char* dirname);
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// Initialize MicroSD
Serial.print("Initializing the MicroSD card module... ");
initMicroSDCard();
// Initialize FTP connection
ftp.OpenConnection();
//list files on SD card
listAndUploadFiles(SD_MMC, "/");
}
void loop() {
// Do nothing here
}
void uploadFile(const char* path) {
ftp.OpenConnection();
ftp.ChangeWorkDir(working_dir); // Change to the desired directory on FTP server
ftp.InitFile("Type I");
ftp.NewFile(path);
File file = SD_MMC.open("/" + String(path));
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
const size_t bufferSize = 512;
uint8_t buffer[bufferSize];
while (file.available()) {
size_t bytesRead = file.read(buffer, bufferSize);
ftp.WriteData(buffer, bytesRead);
Serial.println("File available");
Serial.println(path);
Serial.println("Uploading file"+ file);
}
ftp.CloseFile();
ftp.CloseConnection();
file.close();
Serial.println("File uploaded successfully");
}
void listAndUploadFiles(fs::FS &fs, const char* dirname) {
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
listAndUploadFiles(fs, file.name()); // Recursively list files in subdirectories
} else {
// Serial.print("Uploading file: ");
Serial.println(file.name());
uploadFile(file.name());
Serial.println("Uploading file" + file);
}
file = root.openNextFile();
}
}
It work like this uploading files to FTP server.
I do want to use subdirectories, but it's not as important atm. I kept the code with recursive list of subdirectories as it seem working in terms of uploading the file from the subdirectory to a root folder of FTP path.
I would also like to upload files only once, so I will have to figure out some mechanism to check whether file was uploaded in the past and skip the upload and only upload new files that haven't been uploaded yet.