From 30de202aeec43da78c7a88a7bea8faa983589065 Mon Sep 17 00:00:00 2001 From: tryhus Date: Fri, 21 Dec 2018 16:49:28 +0100 Subject: [PATCH 1/5] add FTP and filesystem API --- examples/FTP/FTP.ino | 153 +++++++++++ examples/FTP/arduino_secrets.h | 9 + src/GSMFTP.cpp | 468 +++++++++++++++++++++++++++++++++ src/GSMFTP.h | 141 ++++++++++ src/GSMFileSystem.cpp | 172 ++++++++++++ src/GSMFileSystem.h | 96 +++++++ src/Modem.cpp | 22 +- 7 files changed, 1060 insertions(+), 1 deletion(-) create mode 100644 examples/FTP/FTP.ino create mode 100644 examples/FTP/arduino_secrets.h create mode 100644 src/GSMFTP.cpp create mode 100644 src/GSMFTP.h create mode 100644 src/GSMFileSystem.cpp create mode 100644 src/GSMFileSystem.h diff --git a/examples/FTP/FTP.ino b/examples/FTP/FTP.ino new file mode 100644 index 0000000..a5f13a7 --- /dev/null +++ b/examples/FTP/FTP.ino @@ -0,0 +1,153 @@ +/* + FTP client + + This sketch connects to a FTP server through a MKR GSM 1400 board. + + Circuit: + * MKR GSM 1400 board + * Antenna + * SIM card with a data plan + + created 21 Dec 2018 + by Tryhus +*/ + +// libraries +#include +#include +#include +#include "arduino_secrets.h" + +// Please enter your sensitive data in the Secret tab or arduino_secrets.h +// PIN Number +const char PINNUMBER[] = SECRET_PINNUMBER; +// APN data +const char GPRS_APN[] = SECRET_GPRS_APN; +const char GPRS_LOGIN[] = SECRET_GPRS_LOGIN; +const char GPRS_PASSWORD[] = SECRET_GPRS_PASSWORD; + +// initialize the library instance +GSMFileSytem fileSystem; +GSMFTP ftp; +GPRS gprs; +GSM gsmAccess; + +void setup() { + // initialize serial communications and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } + + Serial.println("Starting Arduino FTP client."); + // connection state + bool connected = false; + + // After starting the modem with GSM.begin() + // attach the shield to the GPRS network with the APN, login and password + while (!connected) { + if ((gsmAccess.begin(PINNUMBER) == GSM_READY) && + (gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) { + connected = true; + } else { + Serial.println("Not connected"); + delay(1000); + } + } +} + +void loop() { + + Serial.println("Connect to FTP server."); + if (ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD) == false) { + Serial.println("Failed to Connect to FTP server."); + } + + Serial.println("Change of directory"); + if (ftp.cd(SECRET_FTP_REMOTE_DIR) == false) { + Serial.println("Failed to change of directory."); + } + + Serial.print("Free space "); + Serial.println(fileSystem.freeSpace()); + + Serial.println("Create remote file : test"); + if (ftp.mkdir("test") == false) { + Serial.println("Failed to create the file."); + } + + Serial.println("Rename remote file : test to test2"); + if (ftp.rename("test", "test2") == false) { + Serial.println("Failed to rename the file."); + } + + Serial.println("Write a binary file in local memory"); + double valueWR = -12.5789876; + double valueRD = 0; + if (fileSystem.write("myFile", &valueWR, sizeof(valueWR)) == false) { + Serial.println("Failed to write file"); + } + + Serial.println("Send the file to the server"); + if (ftp.upload("myFile", "myFileToServer") == false) { + Serial.println("Failed to upload the file."); + } + + Serial.println("Retreive the file from the server to local memory"); + if (ftp.download("myFileToServer", "myFileToLocalMemory") == false) { + Serial.println("Failed to download the file."); + } + + Serial.println("Check that the original file is identical to the one that was received"); + if (fileSystem.read("myFileToLocalMemory", &valueRD, sizeof(valueRD)) == false) { + Serial.println("Failed to read file"); + } + else if (valueWR != valueRD) { + Serial.println("Failed to read file, value is corrupted"); + } + + Serial.print("Free space "); + Serial.println(fileSystem.freeSpace()); + + Serial.println("Display local files"); + if (fileSystem.ls(true) == false) { + Serial.println("Failed to display local files"); + } + + Serial.println("Remove local files"); + for (int i = 0; i < fileSystem.fileCount(); ++i) { + fileSystem.remove(fileSystem.file(i).name); + } + + Serial.println("Display local files"); + if (fileSystem.ls(true) == false) { + Serial.println("Failed to display local files"); + } + + Serial.println("Display remote files"); + if (ftp.ls(true) == false) { + Serial.println("Failed to display files."); + } + + Serial.println("Delete the created files"); + if (ftp.remove("test2") == false) { + Serial.println("Failed to remove files : test2."); + } + if (ftp.remove("myFileToServer") == false) { + Serial.println("Failed to remove files : myFileToServer."); + } + + Serial.println("Display remote files"); + if (ftp.ls(true) == false) { + Serial.println("Failed to display files."); + } + + Serial.println("Disconnect to FTP server"); + if (ftp.disconnect() == false) { + Serial.println("Failed to disconnect."); + } + + for (;;) + ; +} + diff --git a/examples/FTP/arduino_secrets.h b/examples/FTP/arduino_secrets.h new file mode 100644 index 0000000..c472300 --- /dev/null +++ b/examples/FTP/arduino_secrets.h @@ -0,0 +1,9 @@ +#define SECRET_PINNUMBER "" +#define SECRET_GPRS_APN "" // replace your GPRS APN +#define SECRET_GPRS_LOGIN "" // replace with your GPRS login +#define SECRET_GPRS_PASSWORD "" // replace with your GPRS password + +#define SECRET_FTP_HOST "" // replace with your FTP host server +#define SECRET_FTP_USER "" // replace with your FTP user +#define SECRET_FTP_PASSWORD "" // replace with your FTP password +#define SECRET_FTP_REMOTE_DIR "/" // replace with your FTP default remote directory diff --git a/src/GSMFTP.cpp b/src/GSMFTP.cpp new file mode 100644 index 0000000..822035b --- /dev/null +++ b/src/GSMFTP.cpp @@ -0,0 +1,468 @@ +/* + This file is part of the MKR GSM library. + Copyright (C) 2017 Arduino AG (http://www.arduino.cc/) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +GSMFTP::GSMFTP(): + _connected(false), + _dirCreated(-1), + _dirChanged(-1), + _fileRemoved(-1) +{ + MODEM.addUrcHandler(this); +} + +GSMFTP::~GSMFTP() +{ + MODEM.removeUrcHandler(this); + _file.clear(); +} + +bool GSMFTP::connect(String hostname, String user, String password) +{ + uint32_t start = millis(); + String command; + + command = "AT+UFTP=1,\"" + hostname + "\""; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1){ + return false; + } + + command = "AT+UFTP=2,\"" + user + "\""; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + command = "AT+UFTP=3,\"" + password + "\""; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + MODEM.send("AT+UFTP=6,0"); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + command = "AT+UDNSRN=0,\"" + hostname + "\""; + MODEM.send(command); + if (MODEM.waitForResponse(10000) != 1) { + return false; + } + + while ((_connected == false) && (millis() - start) < c_connectionTimeout) { + MODEM.send("AT+UFTPC=1"); + if (MODEM.waitForResponse(100) != 1) { + continue; + } + for (int i = 0; i < 30; ++i) { + delay(100); + MODEM.ready(); + if (_connected == true){ + break; + } + } + } + + return _connected; +} + +bool GSMFTP::disconnect() +{ + uint32_t start = millis(); + while ((_connected == true) && (millis() - start) < 10000) { + MODEM.send("AT+UFTPC=0"); + if (MODEM.waitForResponse(100) != 1) { + continue; + } + for (int i = 0; i < 30; ++i) { + delay(100); + MODEM.ready(); + if (_connected == false) { + break; + } + } + } + + return !_connected; +} + +bool GSMFTP::ls(bool show, uint32_t timeout) +{ + if (_connected == true) { + _file.ready = false; + _file.clear(); + uint32_t start = millis(); + + MODEM.send("AT+UFTPC=13"); + + while (_file.ready == false) { + MODEM.poll(); + if ((millis() - start) > timeout) { + _file.ready = true; + return false; + } + } + + if (show == true) { + for (int i = 0; i < _file.count; ++i) { + _file.show(i); + } + } + return true; + } + else{ + return false; + } +} + +bool GSMFTP::mkdir(const String& name, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false){ + return false; + } + _dirCreated = -2; + String command = "AT+UFTPC=10,\"" + name + "\""; + + while ((millis() - start) < timeout) { + if (_dirCreated == -2) { + _dirCreated = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_dirCreated == 0) { + _dirCreated = -2; + MODEM.send(command); + } + else if (_dirCreated == 1) { + return true; + } + } + return false; +} + +bool GSMFTP::remove(const String& name, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false) { + return false; + } + + _fileRemoved = -2; + String command = "AT+UFTPC=2,\"" + name + "\""; + + while ((millis() - start) < timeout) { + if (_fileRemoved == -2) { + _fileRemoved = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_fileRemoved == 0) { + _fileRemoved = -2; + MODEM.send(command); + }else if (_fileRemoved == 1) { + return true; + } + } + return false; +} + +bool GSMFTP::rename(const String& oldName, const String& name, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false) { + return false; + } + _fileRenamed= -2; + String command = "AT+UFTPC=3,\"" + oldName + "\",\"" + name + "\""; + + while ((millis() - start) < timeout) { + if (_fileRenamed == -2) { + _fileRenamed = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_fileRenamed == 0) { + _fileRenamed = -2; + MODEM.send(command); + } + else if (_fileRenamed == 1) { + return true; + } + } + return false; +} + +bool GSMFTP::download(const String& remoteFileName, const String& localFileName, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false) { + return false; + } + _fileDownloaded = -2; + String command = "AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""; + + while ((millis() - start) < timeout) { + if (_fileDownloaded == -2) { + _fileDownloaded = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_fileDownloaded == 0) { + _fileDownloaded = -2; + MODEM.send(command); + } + else if (_fileDownloaded == 1) { + return true; + } + } + return false; +} + +bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false) { + return false; + } + _fileUploaded = -2; + String command = "AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""; + + while ((millis() - start) < timeout) { + if (_fileUploaded == -2) { + _fileUploaded = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_fileUploaded == 0) { + _fileUploaded = -2; + MODEM.send(command); + } + else if (_fileUploaded == 1) { + return true; + } + } + return false; +} + +bool GSMFTP::cd(const String& name, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false) { + return false; + } + _dirChanged = -2; + String command = "AT+UFTPC=8,\"" + name + "\""; + + while ((millis() - start) < timeout) { + if (_dirChanged == -2) { + _dirChanged = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_dirChanged == 0) { + _dirChanged = -2; + MODEM.send(command); + } + else if (_dirChanged == 1) { + return true; + } + } + return false; +} + +GSMFTP::FTPFileElem GSMFTP::file(uint16_t i) +{ + if (i < _file.count) { + return _file.e[i]; + } + else { + return FTPFileElem(); + } +} + +void GSMFTP::FTPFile::append(const FTPFileElem& elem) +{ + FTPFileElem* tmp = new FTPFileElem[count + 1]; + for (int i = 0; i < count; ++i) { + tmp[i] = e[i]; + } + tmp[count] = elem; + if (e != nullptr) { + delete[] e; + } + e = tmp; + count++; +} + +void GSMFTP::FTPFile::clear() { + if (e != nullptr) { + delete[] e; + e = nullptr; + } + count = 0; +} + +void GSMFTP::FTPFile::show(int i) +{ + if (i >= count) { + return; + } + + Serial.print(e[i].permissions); + Serial.print(" "); + Serial.print(e[i].number); + Serial.print(" "); + Serial.print(e[i].user); + Serial.print(" "); + Serial.print(e[i].group); + Serial.print(" "); + Serial.print(e[i].size); + Serial.print(" "); + Serial.print(e[i].lastModified); + Serial.print(" "); + Serial.print(e[i].name); + Serial.println(); +} + +void GSMFTP::FTPFile::parse(const String& str) +{ + if (str == "\"") { + ready = true; + } + else { + String res = str; + if (count == 0) { + int i = res.indexOf('"'); + if (i < 0) { + ready = true; + return; + } + res = res.substring(i + 1); + } + FTPFileElem elem; + for (int i = 0; i < 7; ++i) + { + String tmp = res; + int j = res.indexOf(" "); + + while (res.charAt(j + 1) == ' ') { + ++j; + } + if (i == 5) { + for (int k = 1; k < 3; ++k) { + j = res.indexOf(" ", j + 1); + while (res.charAt(j + 1) == ' ') { + ++j; + } + } + } + + if (j > 0) { + tmp = res.substring(0, j + 1); + tmp.trim(); + res = res.substring(j + 1); + } + + switch (i) + { + case 0: + elem.permissions = tmp; + break; + case 1: + elem.number = tmp.toInt(); + break; + case 2: + elem.user = tmp; + break; + case 3: + elem.group = tmp; + break; + case 4: + elem.size = tmp.toInt(); + break; + case 5: + elem.lastModified = tmp; + break; + case 6: + elem.name = tmp; + break; + default: + break; + } + } + append(elem); + } +} + +void GSMFTP::handleUrc(const String& urc) +{ + if (urc.startsWith("+UUFTPCR: 1,1")) { + _connected = true; + } + else if (urc.startsWith("+UUFTPCR: 0,1")) { + _connected = false; + } + else if (urc.startsWith("+UUFTPCD: 13,")) { + ; + } + else if (urc.startsWith("+UUFTPCR: 10,")) { + _dirCreated = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 8,")) { + _dirChanged = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 2,")) { + _fileRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 3,")) { + _fileRenamed = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 4,")) { + _fileDownloaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 5,")) { + _fileUploaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + + if (_file.ready == false) { + _file.parse(urc); + } +} diff --git a/src/GSMFTP.h b/src/GSMFTP.h new file mode 100644 index 0000000..7a17093 --- /dev/null +++ b/src/GSMFTP.h @@ -0,0 +1,141 @@ +/* + This file is part of the MKR GSM library. + Copyright (C) 2017 Arduino AG (http://www.arduino.cc/) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _GSM_FTP_H_INCLUDED +#define _GSM_FTP_H_INCLUDED + +#include + +class GSMFTP : public ModemUrcHandler { + +public: + struct FTPFileElem + { + String permissions; + uint32_t size; + uint32_t number; + String user; + String group; + String lastModified; + String name; + + FTPFileElem() :size(0), number(0) + {} + }; + + GSMFTP(); + virtual ~GSMFTP(); + + /** Connect to a FTP server + @param hostname FTP server hostname + @param user FTP user name + @param password FTP password + @return true if no error + */ + bool connect(String hostname, String user, String password); + /** Disconnect to the FTP server + @return true if no error + */ + bool disconnect(); + /** Get informations of remote directory + @param show if true, display information of files + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool ls(bool show=false, uint32_t timeout=10000); + /** Get file number of remote directory + @return number of file read after the call of @ref ls function + */ + uint32_t fileCount() { return _file.count; } + /** Get a file element + @param i index of the file element array to retreive + @return file element + */ + FTPFileElem file(uint16_t i); + /** Create directory on the FTP server + @param name name of the directory to create + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool mkdir(const String& name, uint32_t timeout=10000); + /** Delete file on the FTP server + @param name name of the file to delete + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool remove(const String&, uint32_t timeout = 10000); + /** Rename file on the FTP server + @param oldName name of the file to rename + @param name new name of the file to rename + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool rename(const String& oldName, const String& name, uint32_t timeout = 10000); + /** Change of the working directory on the FTP server + @param path new working directory to move on + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool cd(const String& path, uint32_t timeout = 10000); + /** Download a file from the FTP server + @param remoteFileName name of file on FTP server to retreive on filesystem + @param localFileName name of the file on filesystem sent from FTP server + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool download(const String& remoteFileName, const String& localFileName, uint32_t timeout = 10000); + /** Upload a file to the FTP server + @param localFileName name of the file on filesystem to send to FTP server + @param remoteFileName name of the file on FTP server sent from filesystem + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool upload(const String& localFileName, const String&remoteFileName, uint32_t timeout = 10000); + +private: + static const uint32_t c_connectionTimeout = 10000; + + struct FTPFile + { + FTPFileElem* e; + uint32_t count; + bool ready; + + void clear(); + void append(const FTPFileElem&); + void show(int); + void parse(const String&); + + FTPFile() :e(nullptr), count(0), ready(true) + {} + }; + + void handleUrc(const String&); + + bool _connected; + int _dirCreated; + int _dirChanged; + int _fileRemoved; + int _fileRenamed; + int _fileDownloaded; + int _fileUploaded; + FTPFile _file; +}; + +#endif diff --git a/src/GSMFileSystem.cpp b/src/GSMFileSystem.cpp new file mode 100644 index 0000000..9e4329a --- /dev/null +++ b/src/GSMFileSystem.cpp @@ -0,0 +1,172 @@ +/* + This file is part of the MKR GSM library. + Copyright (C) 2017 Arduino AG (http://www.arduino.cc/) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +GSMFileSytem::GSMFileSytem() +{ +} + +GSMFileSytem::~GSMFileSytem() +{ + _file.clear(); +} + +bool GSMFileSytem::ls(bool show, uint32_t timeout) +{ + uint32_t start = millis(); + _file.clear(); + String response; + MODEM.send("AT+ULSTFILE="); + + if (MODEM.waitForResponse(timeout, &response) == 1) { + _file.parse(response); + }else { + return false; + } + + for (int i = 0; i < _file.count; ++i){ + if ((millis() - start) > timeout) { + return false; + } + String command = "AT+ULSTFILE=2,\"" + _file.e[i].name + "\""; + MODEM.send(command); + + if (MODEM.waitForResponse(1000, &response) == 1) { + if (response.startsWith("+ULSTFILE: ")) { + _file.e[i].size = response.substring(11).toInt(); + } + } + if (show == true) { + _file.show(i); + } + } + + return true; +} + +bool GSMFileSytem::remove(const String& name) +{ + String command = "AT+UDELFILE=\"" + name + "\""; + MODEM.send(command); + if (MODEM.waitForResponse(10000) == 1) { + return true; + }else { + return false; + } +} + +uint32_t GSMFileSytem::freeSpace() +{ + uint32_t res = 0; + String response; + MODEM.send("AT+ULSTFILE=1"); + + if (MODEM.waitForResponse(1000, &response) == 1) { + if (response.startsWith("+ULSTFILE: ")) { + res = response.substring(11).toInt(); + } + } + return res; +} + +bool GSMFileSytem::write(const String& fileName, void* data, size_t size) +{ + String command = "AT+UDWNFILE=\"" + fileName + "\"," + size; + MODEM.send(command); + + if (MODEM.waitForPrompt() == 1) { + MODEM.write((const uint8_t*)data, size); + if (MODEM.waitForResponse(10000) == 1) { + return true; + } + } + return false; +} + +bool GSMFileSytem::read(const String& fileName, void* data, size_t size) +{ + String response; + String command = "AT+URDFILE=\"" + fileName + "\""; + MODEM.send(command); + + if (MODEM.waitForResponse(10000, &response) == 1) { + memcpy(data, response.c_str(), size); + return true; + } + return false; +} + +GSMFileSytem::FileElem GSMFileSytem::file(uint16_t i) +{ + if (i < _file.count) { + return _file.e[i]; + }else { + return FileElem(); + } +} + +void GSMFileSytem::File::append(const FileElem& elem) +{ + FileElem* tmp = new FileElem[count + 1]; + for (int i = 0; i < count; ++i) { + tmp[i] = e[i]; + } + tmp[count] = elem; + if (e != nullptr) { + delete[] e; + } + e = tmp; + count++; +} + +void GSMFileSytem::File::clear() { + if (e != nullptr) { + delete[] e; + e = nullptr; + } + count = 0; +} + +void GSMFileSytem::File::show(int i) +{ + if (i >= count) { + return; + } + Serial.print(e[i].name); + Serial.print(" "); + Serial.println(e[i].size); +} + +void GSMFileSytem::File::parse(const String& str) +{ + String res = str; + int i = res.indexOf('"')+1; + int j = res.indexOf('"', i); + + while ((i > 0) && (j > 0)){ + FileElem elem; + elem.name = res.substring(i, j); + res = res.substring(j + 1); + append(elem); + i = res.indexOf('"') + 1; + j = res.indexOf('"', i); + } +} diff --git a/src/GSMFileSystem.h b/src/GSMFileSystem.h new file mode 100644 index 0000000..51a8698 --- /dev/null +++ b/src/GSMFileSystem.h @@ -0,0 +1,96 @@ +/* + This file is part of the MKR GSM library. + Copyright (C) 2017 Arduino AG (http://www.arduino.cc/) + + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _GSM_FILE_SYSTEM_H_INCLUDED +#define _GSM_FILE_SYSTEM_H_INCLUDED + +#include + +class GSMFileSytem { + +public: + struct FileElem + { + String name; + uint32_t size; + + FileElem() :size(0) + {} + }; + + GSMFileSytem(); + virtual ~GSMFileSytem(); + /** Get name and size of all the file system files + @param show if true, display name and size of the files + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool ls(bool show = false, uint32_t timeout = 10000); + /** Delete a file of the file system + @param name name of the file to delete + @return true if no error + */ + bool remove(const String& name); + /** Get file number of the file system + @return number of file read after the call of @ref ls function + */ + uint32_t fileCount() { return _file.count; } + /** Get a file element + @param i index of the file element array to retreive + @return file element + */ + FileElem file(uint16_t i); + /** Get a free space of the file system in bytes + @return free space + */ + uint32_t freeSpace(); + /** Create a file with data in the filesystem + @param name name of the file to create + @param data address of the data to write + @param size size of the data to write + @return true if no error + */ + bool write(const String& name, void* data, size_t size); + /** Read a file in the filesystem + @param name name of the file to read + @param data address of read data + @param size size of the data to read + @return true if no error + */ + bool read(const String& name, void* data, size_t size); + +private: + struct File + { + FileElem* e; + uint32_t count; + + void clear(); + void append(const FileElem&); + void show(int); + void parse(const String&); + + File() :e(nullptr), count(0) + {} + }; + + File _file; +}; + +#endif diff --git a/src/Modem.cpp b/src/Modem.cpp index 2e73cd1..4753584 100644 --- a/src/Modem.cpp +++ b/src/Modem.cpp @@ -279,7 +279,27 @@ void ModemClass::poll() } case AT_RECEIVING_RESPONSE: { - if (c == '\n') { + if (_buffer.startsWith("+URDFILE: ")){ + int i = _buffer.indexOf(',') + 1; + if (i < 0) { + return; + } + int j = _buffer.indexOf(',', i); + if (j < 0) { + return; + } + int size = _buffer.substring(i, j).toInt(); + if (size == (_buffer.length() - j - 2)) { + _buffer = _buffer.substring(j + 2); + *_responseDataStorage = _buffer; + _responseDataStorage = nullptr; + _atCommandState = AT_COMMAND_IDLE; + _buffer = ""; + _ready = 1; + return; + } + } + else if (c == '\n') { _lastResponseOrUrcMillis = millis(); int responseResultIndex = _buffer.lastIndexOf("OK\r\n"); From 810e98c3e8b58e4b956852b6dbaa57f5800c0578 Mon Sep 17 00:00:00 2001 From: tryhus Date: Wed, 9 Jan 2019 23:14:04 +0100 Subject: [PATCH 2/5] Add FTP passive mode, server port, remove directory function Some FTP server doesn't allow files exchange in active mode. Passive mode can solve this problem. Passive mode is enable by default. --- examples/FTP/FTP.ino | 22 ++++++------ examples/FTP/arduino_secrets.h | 11 +++--- src/GSMFTP.cpp | 64 +++++++++++++++++++++++++++++++--- src/GSMFTP.h | 27 +++++++++++--- 4 files changed, 99 insertions(+), 25 deletions(-) diff --git a/examples/FTP/FTP.ino b/examples/FTP/FTP.ino index a5f13a7..f317b59 100644 --- a/examples/FTP/FTP.ino +++ b/examples/FTP/FTP.ino @@ -59,7 +59,7 @@ void setup() { void loop() { Serial.println("Connect to FTP server."); - if (ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD) == false) { + if (ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD, SECRET_FTP_PORT) == false) { Serial.println("Failed to Connect to FTP server."); } @@ -71,14 +71,14 @@ void loop() { Serial.print("Free space "); Serial.println(fileSystem.freeSpace()); - Serial.println("Create remote file : test"); + Serial.println("Create remote directory : test"); if (ftp.mkdir("test") == false) { - Serial.println("Failed to create the file."); + Serial.println("Failed to create the directory."); } - Serial.println("Rename remote file : test to test2"); + Serial.println("Rename remote directory : test to test2"); if (ftp.rename("test", "test2") == false) { - Serial.println("Failed to rename the file."); + Serial.println("Failed to rename the directory."); } Serial.println("Write a binary file in local memory"); @@ -87,15 +87,17 @@ void loop() { if (fileSystem.write("myFile", &valueWR, sizeof(valueWR)) == false) { Serial.println("Failed to write file"); } - + Serial.println("Send the file to the server"); if (ftp.upload("myFile", "myFileToServer") == false) { Serial.println("Failed to upload the file."); + ftp.printError(); } - + Serial.println("Retreive the file from the server to local memory"); if (ftp.download("myFileToServer", "myFileToLocalMemory") == false) { Serial.println("Failed to download the file."); + ftp.printError(); } Serial.println("Check that the original file is identical to the one that was received"); @@ -129,11 +131,11 @@ void loop() { Serial.println("Failed to display files."); } - Serial.println("Delete the created files"); - if (ftp.remove("test2") == false) { + Serial.println("Delete the created file and directory"); + if (ftp.removeDirectory("test2") == false) { Serial.println("Failed to remove files : test2."); } - if (ftp.remove("myFileToServer") == false) { + if (ftp.removeFile("myFileToServer") == false) { Serial.println("Failed to remove files : myFileToServer."); } diff --git a/examples/FTP/arduino_secrets.h b/examples/FTP/arduino_secrets.h index c472300..6408609 100644 --- a/examples/FTP/arduino_secrets.h +++ b/examples/FTP/arduino_secrets.h @@ -1,9 +1,10 @@ #define SECRET_PINNUMBER "" #define SECRET_GPRS_APN "" // replace your GPRS APN -#define SECRET_GPRS_LOGIN "" // replace with your GPRS login +#define SECRET_GPRS_LOGIN "" // replace with your GPRS login #define SECRET_GPRS_PASSWORD "" // replace with your GPRS password -#define SECRET_FTP_HOST "" // replace with your FTP host server -#define SECRET_FTP_USER "" // replace with your FTP user -#define SECRET_FTP_PASSWORD "" // replace with your FTP password -#define SECRET_FTP_REMOTE_DIR "/" // replace with your FTP default remote directory +#define SECRET_FTP_HOST "" // replace with your FTP host server +#define SECRET_FTP_USER "" // replace with your FTP user +#define SECRET_FTP_PASSWORD "" // replace with your FTP password +#define SECRET_FTP_PORT 21 // replace with your FTP port (optional) +#define SECRET_FTP_REMOTE_DIR "/" // replace with your FTP default remote directory diff --git a/src/GSMFTP.cpp b/src/GSMFTP.cpp index 822035b..c208a80 100644 --- a/src/GSMFTP.cpp +++ b/src/GSMFTP.cpp @@ -34,7 +34,7 @@ GSMFTP::~GSMFTP() _file.clear(); } -bool GSMFTP::connect(String hostname, String user, String password) +bool GSMFTP::connect(String hostname, String user, String password, uint16_t port, bool passiveMode) { uint32_t start = millis(); String command; @@ -57,7 +57,14 @@ bool GSMFTP::connect(String hostname, String user, String password) return false; } - MODEM.send("AT+UFTP=6,0"); + command = (passiveMode == true) ? "AT+UFTP=6,1" : "AT+UFTP=6,0"; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + command = "AT+UFTP=7," + String(port); + MODEM.send(command); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -163,7 +170,7 @@ bool GSMFTP::mkdir(const String& name, uint32_t timeout) return false; } -bool GSMFTP::remove(const String& name, uint32_t timeout) +bool GSMFTP::removeFile(const String& name, uint32_t timeout) { uint32_t start = millis(); if (_connected == false) { @@ -192,6 +199,36 @@ bool GSMFTP::remove(const String& name, uint32_t timeout) return false; } +bool GSMFTP::removeDirectory(const String& name, uint32_t timeout) +{ + uint32_t start = millis(); + if (_connected == false) { + return false; + } + + _dirRemoved = -2; + String command = "AT+UFTPC=11,\"" + name + "\""; + + while ((millis() - start) < timeout) { + if (_dirRemoved == -2) { + _dirRemoved = -1; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + } + MODEM.poll(); + if (_dirRemoved == 0) { + _dirRemoved = -2; + MODEM.send(command); + } + else if (_dirRemoved == 1) { + return true; + } + } + return false; +} + bool GSMFTP::rename(const String& oldName, const String& name, uint32_t timeout) { uint32_t start = millis(); @@ -241,7 +278,6 @@ bool GSMFTP::download(const String& remoteFileName, const String& localFileName, MODEM.poll(); if (_fileDownloaded == 0) { _fileDownloaded = -2; - MODEM.send(command); } else if (_fileDownloaded == 1) { return true; @@ -270,7 +306,6 @@ bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, u MODEM.poll(); if (_fileUploaded == 0) { _fileUploaded = -2; - MODEM.send(command); } else if (_fileUploaded == 1) { return true; @@ -308,6 +343,22 @@ bool GSMFTP::cd(const String& name, uint32_t timeout) return false; } +void GSMFTP::printError() +{ + String res = "FTP last error : "; + String response; + + MODEM.send("AT+UFTPER"); + + if ((MODEM.waitForResponse(1000, &response) == 1) && + (response.startsWith("+UFTPER:"))) { + res += response.substring(8); + }else { + res += "no response"; + } + Serial.println(res); +} + GSMFTP::FTPFileElem GSMFTP::file(uint16_t i) { if (i < _file.count) { @@ -452,6 +503,9 @@ void GSMFTP::handleUrc(const String& urc) else if (urc.startsWith("+UUFTPCR: 2,")) { _fileRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; } + else if (urc.startsWith("+UUFTPCR: 11,")) { + _dirRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } else if (urc.startsWith("+UUFTPCR: 3,")) { _fileRenamed = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; } diff --git a/src/GSMFTP.h b/src/GSMFTP.h index 7a17093..db3929e 100644 --- a/src/GSMFTP.h +++ b/src/GSMFTP.h @@ -46,9 +46,11 @@ class GSMFTP : public ModemUrcHandler { @param hostname FTP server hostname @param user FTP user name @param password FTP password + @param port FTP server port + @param passiveMode true if passive mode is active @return true if no error */ - bool connect(String hostname, String user, String password); + bool connect(String hostname, String user, String password, uint16_t port, bool passiveMode=true); /** Disconnect to the FTP server @return true if no error */ @@ -74,12 +76,18 @@ class GSMFTP : public ModemUrcHandler { @return true if no error */ bool mkdir(const String& name, uint32_t timeout=10000); + /** Delete directory on the FTP server + @param name name of the directory to delete + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool removeDirectory(const String&, uint32_t timeout = 10000); /** Delete file on the FTP server - @param name name of the file to delete - @param timeout maximum time allow to execute the function - @return true if no error + @param name name of the file to delete + @param timeout maximum time allow to execute the function + @return true if no error */ - bool remove(const String&, uint32_t timeout = 10000); + bool removeFile(const String&, uint32_t timeout = 10000); /** Rename file on the FTP server @param oldName name of the file to rename @param name new name of the file to rename @@ -108,6 +116,14 @@ class GSMFTP : public ModemUrcHandler { */ bool upload(const String& localFileName, const String&remoteFileName, uint32_t timeout = 10000); + /** Print the error class and code of the last FTP operation + @brief + 0,0 mean no error otherwise {error class},{error code}. + For the description refer to the documention : + https://www.u-blox.com/sites/default/files/u-blox-CEL_ATCommands_%28UBX-13002752%29.pdf + */ + void printError(); + private: static const uint32_t c_connectionTimeout = 10000; @@ -132,6 +148,7 @@ class GSMFTP : public ModemUrcHandler { int _dirCreated; int _dirChanged; int _fileRemoved; + int _dirRemoved; int _fileRenamed; int _fileDownloaded; int _fileUploaded; From 015956196c2da8146499bfab3cab08c8342fdd57 Mon Sep 17 00:00:00 2001 From: tryhus Date: Fri, 11 Jan 2019 18:33:06 +0100 Subject: [PATCH 3/5] Add non blocking functions for FTP download/upload non blocking function can be useful for large file transfer --- .gitignore | 11 ++ examples/FTP/FTP.ino | 103 ++++++++++-- src/GSMFTP.cpp | 381 ++++++++++++++++++++++++++---------------- src/GSMFTP.h | 168 +++++++++++++------ src/GSMFileSystem.cpp | 134 ++++++++------- src/GSMFileSystem.h | 95 +++++++---- 6 files changed, 584 insertions(+), 308 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ddb1b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +#exclude everything except some directory +*.suo +*.d +*.o +**/Release/** +**/__vm/** +project/**/Debug/** +project/**/Release/** +project/**/__vm/** +packages/arduino/tools +staging \ No newline at end of file diff --git a/examples/FTP/FTP.ino b/examples/FTP/FTP.ino index f317b59..554ab0f 100644 --- a/examples/FTP/FTP.ino +++ b/examples/FTP/FTP.ino @@ -26,8 +26,10 @@ const char GPRS_APN[] = SECRET_GPRS_APN; const char GPRS_LOGIN[] = SECRET_GPRS_LOGIN; const char GPRS_PASSWORD[] = SECRET_GPRS_PASSWORD; +//this file must be present in the remote directory SECRET_FTP_REMOTE_DIR +const String c_downloadFileName = "downloadFile"; + // initialize the library instance -GSMFileSytem fileSystem; GSMFTP ftp; GPRS gprs; GSM gsmAccess; @@ -57,19 +59,22 @@ void setup() { } void loop() { + GSMFileSystemElem localFile; + GSMFTPElem remoteFile; Serial.println("Connect to FTP server."); if (ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD, SECRET_FTP_PORT) == false) { Serial.println("Failed to Connect to FTP server."); + ftp.printError(); } - + Serial.println("Change of directory"); if (ftp.cd(SECRET_FTP_REMOTE_DIR) == false) { Serial.println("Failed to change of directory."); } - + Serial.print("Free space "); - Serial.println(fileSystem.freeSpace()); + Serial.println(FILESYSTEM.freeSpace()); Serial.println("Create remote directory : test"); if (ftp.mkdir("test") == false) { @@ -84,7 +89,7 @@ void loop() { Serial.println("Write a binary file in local memory"); double valueWR = -12.5789876; double valueRD = 0; - if (fileSystem.write("myFile", &valueWR, sizeof(valueWR)) == false) { + if (FILESYSTEM.write("myFile", &valueWR, sizeof(valueWR)) == false) { Serial.println("Failed to write file"); } @@ -95,13 +100,13 @@ void loop() { } Serial.println("Retreive the file from the server to local memory"); - if (ftp.download("myFileToServer", "myFileToLocalMemory") == false) { + if (ftp.download("myFileToLocalMemory", "myFileToServer") == false) { Serial.println("Failed to download the file."); ftp.printError(); } Serial.println("Check that the original file is identical to the one that was received"); - if (fileSystem.read("myFileToLocalMemory", &valueRD, sizeof(valueRD)) == false) { + if (FILESYSTEM.read("myFileToLocalMemory", &valueRD, sizeof(valueRD)) == false) { Serial.println("Failed to read file"); } else if (valueWR != valueRD) { @@ -109,25 +114,25 @@ void loop() { } Serial.print("Free space "); - Serial.println(fileSystem.freeSpace()); + Serial.println(FILESYSTEM.freeSpace()); Serial.println("Display local files"); - if (fileSystem.ls(true) == false) { + if (FILESYSTEM.ls(localFile, true) == false) { Serial.println("Failed to display local files"); } Serial.println("Remove local files"); - for (int i = 0; i < fileSystem.fileCount(); ++i) { - fileSystem.remove(fileSystem.file(i).name); + if (FILESYSTEM.remove(localFile) == false) { + Serial.println("Failed to remove file"); } - + Serial.println("Display local files"); - if (fileSystem.ls(true) == false) { + if (FILESYSTEM.ls(localFile, true) == false) { Serial.println("Failed to display local files"); } Serial.println("Display remote files"); - if (ftp.ls(true) == false) { + if (ftp.ls(remoteFile, true) == false) { Serial.println("Failed to display files."); } @@ -140,16 +145,82 @@ void loop() { } Serial.println("Display remote files"); - if (ftp.ls(true) == false) { + if (ftp.ls(remoteFile, true) == false) { + Serial.println("Failed to display files."); + } + + //--- Test download/upload a large file with non blocking function --- + + Serial.println(); + Serial.println("Download a file with non blocking function"); + downloadFileNonBlocking("downloadedFile", c_downloadFileName); + + Serial.println("Display local files"); + if (FILESYSTEM.ls(localFile, true) == false) { + Serial.println("Failed to display local files"); + } + + Serial.println("Upload a file with non blocking function"); + uploadFileNonBlocking("downloadedFile", "uploadFile"); + + Serial.println("Display remote files"); + if (ftp.ls(remoteFile, true) == false) { Serial.println("Failed to display files."); } + Serial.println("Remove local and remote files"); + if (FILESYSTEM.remove("downloadedFile") == false) { + Serial.println("Failed to remove file"); + } + if (ftp.removeFile("uploadFile") == false) { + Serial.println("Failed to remove files : myFileToServer."); + } + Serial.println("Disconnect to FTP server"); if (ftp.disconnect() == false) { Serial.println("Failed to disconnect."); } - + for (;;) ; } +//Example of non blocking download functions +void downloadFileNonBlocking(const String localFileName, const String remoteFileName) { + + Serial.println("Retreive the file from the server to local memory"); + //Start download + if (ftp.downloadStart(localFileName, remoteFileName) == false) { + Serial.println("Failed to start download."); + ftp.printError(); + } + + //update download + while (ftp.downloadReady(localFileName, true) == 0) + { + //do some job + } +} + +//Example of non blocking upload functions +void uploadFileNonBlocking(const String localFileName, const String remoteFileName) { + + Serial.println("Send the file to the server from local memory"); + if (ftp.uploadStart(localFileName, remoteFileName) == false) { + Serial.println("Failed to start upload."); + ftp.printError(); + } + + int res = 0; + while (res == 0){ + res = ftp.uploadReady(); + if (res == 1) { + Serial.println("Upload finished."); + } + else if (res < 0) { + Serial.println("Upload error."); + ftp.printError(); + } + //do some job + } +} diff --git a/src/GSMFTP.cpp b/src/GSMFTP.cpp index c208a80..768d8e5 100644 --- a/src/GSMFTP.cpp +++ b/src/GSMFTP.cpp @@ -18,12 +18,16 @@ */ #include +#include GSMFTP::GSMFTP(): _connected(false), _dirCreated(-1), _dirChanged(-1), - _fileRemoved(-1) + _fileRemoved(-1), + _downloadDisplayTimeRef(0), + _downloadRemoteFileSize(0), + _fileInfo(nullptr) { MODEM.addUrcHandler(this); } @@ -31,46 +35,39 @@ GSMFTP::GSMFTP(): GSMFTP::~GSMFTP() { MODEM.removeUrcHandler(this); - _file.clear(); } bool GSMFTP::connect(String hostname, String user, String password, uint16_t port, bool passiveMode) { uint32_t start = millis(); - String command; - command = "AT+UFTP=1,\"" + hostname + "\""; - MODEM.send(command); + MODEM.send("AT+UFTP=1,\"" + hostname + "\""); if (MODEM.waitForResponse(100) != 1){ return false; } - command = "AT+UFTP=2,\"" + user + "\""; - MODEM.send(command); + MODEM.send("AT+UFTP=2,\"" + user + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } - command = "AT+UFTP=3,\"" + password + "\""; - MODEM.send(command); + MODEM.send("AT+UFTP=3,\"" + password + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } - command = (passiveMode == true) ? "AT+UFTP=6,1" : "AT+UFTP=6,0"; + String command = (passiveMode == true) ? "AT+UFTP=6,1" : "AT+UFTP=6,0"; MODEM.send(command); if (MODEM.waitForResponse(100) != 1) { return false; } - command = "AT+UFTP=7," + String(port); - MODEM.send(command); + MODEM.sendf("AT+UFTP=7,%d", port); if (MODEM.waitForResponse(100) != 1) { return false; } - - command = "AT+UDNSRN=0,\"" + hostname + "\""; - MODEM.send(command); + + MODEM.send("AT+UDNSRN=0,\"" + hostname + "\""); if (MODEM.waitForResponse(10000) != 1) { return false; } @@ -112,26 +109,26 @@ bool GSMFTP::disconnect() return !_connected; } -bool GSMFTP::ls(bool show, uint32_t timeout) +bool GSMFTP::ls(GSMFTPElem& file, bool show, uint32_t timeout) { if (_connected == true) { - _file.ready = false; - _file.clear(); + file.clear(); + _fileInfo = &file; uint32_t start = millis(); MODEM.send("AT+UFTPC=13"); - while (_file.ready == false) { + while (_fileInfo != nullptr) { MODEM.poll(); if ((millis() - start) > timeout) { - _file.ready = true; + _fileInfo = nullptr; return false; } } if (show == true) { - for (int i = 0; i < _file.count; ++i) { - _file.show(i); + for (int i = 0; i < file.count(); ++i) { + file.show(i); } } return true; @@ -141,6 +138,34 @@ bool GSMFTP::ls(bool show, uint32_t timeout) } } +bool GSMFTP::ls(GSMFTPElem& file, const String name, bool show, uint32_t timeout) +{ + if (_connected == true) { + file.clear(); + _fileInfo = &file; + uint32_t start = millis(); + MODEM.send("AT+UFTPC = 13,\"" + name + "\""); + + while (_fileInfo != nullptr) { + MODEM.poll(); + if ((millis() - start) > timeout) { + _fileInfo = nullptr; + return false; + } + } + + if (show == true) { + for (int i = 0; i < file.count(); ++i) { + file.show(i); + } + } + return true; + } + else { + return false; + } +} + bool GSMFTP::mkdir(const String& name, uint32_t timeout) { uint32_t start = millis(); @@ -148,12 +173,11 @@ bool GSMFTP::mkdir(const String& name, uint32_t timeout) return false; } _dirCreated = -2; - String command = "AT+UFTPC=10,\"" + name + "\""; while ((millis() - start) < timeout) { if (_dirCreated == -2) { _dirCreated = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=10,\"" + name + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -161,9 +185,7 @@ bool GSMFTP::mkdir(const String& name, uint32_t timeout) MODEM.poll(); if (_dirCreated == 0) { _dirCreated = -2; - MODEM.send(command); - } - else if (_dirCreated == 1) { + }else if (_dirCreated == 1) { return true; } } @@ -178,12 +200,11 @@ bool GSMFTP::removeFile(const String& name, uint32_t timeout) } _fileRemoved = -2; - String command = "AT+UFTPC=2,\"" + name + "\""; while ((millis() - start) < timeout) { if (_fileRemoved == -2) { _fileRemoved = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=2,\"" + name + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -191,7 +212,6 @@ bool GSMFTP::removeFile(const String& name, uint32_t timeout) MODEM.poll(); if (_fileRemoved == 0) { _fileRemoved = -2; - MODEM.send(command); }else if (_fileRemoved == 1) { return true; } @@ -207,12 +227,11 @@ bool GSMFTP::removeDirectory(const String& name, uint32_t timeout) } _dirRemoved = -2; - String command = "AT+UFTPC=11,\"" + name + "\""; while ((millis() - start) < timeout) { if (_dirRemoved == -2) { _dirRemoved = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=11,\"" + name + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -220,9 +239,7 @@ bool GSMFTP::removeDirectory(const String& name, uint32_t timeout) MODEM.poll(); if (_dirRemoved == 0) { _dirRemoved = -2; - MODEM.send(command); - } - else if (_dirRemoved == 1) { + }else if (_dirRemoved == 1) { return true; } } @@ -236,12 +253,11 @@ bool GSMFTP::rename(const String& oldName, const String& name, uint32_t timeout) return false; } _fileRenamed= -2; - String command = "AT+UFTPC=3,\"" + oldName + "\",\"" + name + "\""; while ((millis() - start) < timeout) { if (_fileRenamed == -2) { _fileRenamed = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=3,\"" + oldName + "\",\"" + name + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -249,28 +265,25 @@ bool GSMFTP::rename(const String& oldName, const String& name, uint32_t timeout) MODEM.poll(); if (_fileRenamed == 0) { _fileRenamed = -2; - MODEM.send(command); - } - else if (_fileRenamed == 1) { + }else if (_fileRenamed == 1) { return true; } } return false; } -bool GSMFTP::download(const String& remoteFileName, const String& localFileName, uint32_t timeout) +bool GSMFTP::download(const String& localFileName, const String& remoteFileName, uint32_t timeout) { uint32_t start = millis(); if (_connected == false) { return false; } _fileDownloaded = -2; - String command = "AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""; while ((millis() - start) < timeout) { if (_fileDownloaded == -2) { _fileDownloaded = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -278,14 +291,64 @@ bool GSMFTP::download(const String& remoteFileName, const String& localFileName, MODEM.poll(); if (_fileDownloaded == 0) { _fileDownloaded = -2; - } - else if (_fileDownloaded == 1) { + }else if (_fileDownloaded == 1) { return true; } } return false; } +bool GSMFTP::downloadStart(const String& localFileName, const String& remoteFileName) +{ + if (_connected == false) { + return false; + } + //get remote file informations + GSMFTPElem remoteFile; + ls(remoteFile, remoteFileName); + _downloadRemoteFileSize = remoteFile.elem(0).size; + _fileDownloaded = -2; + + MODEM.send("AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + return true; +} + +int GSMFTP::downloadReady(const String& localFileName, bool showProgression) +{ + if (_connected == false) { + return -1; + } + MODEM.poll(); + + if (_fileDownloaded == 0) { + _fileDownloaded = -2; + if (showProgression == true) { + Serial.println("Failed to download the file."); + } + return -1; + }else if (_fileDownloaded == 1) { + _fileDownloaded = -2; + if (showProgression == true) { + Serial.println("Download 100%"); + } + return 1; + }else if ((showProgression == true) && + ((millis() - _downloadDisplayTimeRef) > 5000)) { + double progress = 0; + if (_downloadRemoteFileSize > 0) { + progress = 100.0*FILESYSTEM.size(localFileName) / (double)(_downloadRemoteFileSize); + } + Serial.print("Download "); + Serial.print(progress); + Serial.println(" %"); + _downloadDisplayTimeRef = millis(); + } + return 0; +} + bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, uint32_t timeout) { uint32_t start = millis(); @@ -293,12 +356,11 @@ bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, u return false; } _fileUploaded = -2; - String command = "AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""; while ((millis() - start) < timeout) { if (_fileUploaded == -2) { _fileUploaded = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -306,14 +368,44 @@ bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, u MODEM.poll(); if (_fileUploaded == 0) { _fileUploaded = -2; - } - else if (_fileUploaded == 1) { + }else if (_fileUploaded == 1) { return true; } } return false; } +bool GSMFTP::uploadStart(const String& localFileName, const String& remoteFileName) +{ + if (_connected == false) { + return false; + } + + _fileUploaded = -2; + MODEM.send("AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + return true; +} + +int GSMFTP::uploadReady() +{ + if (_connected == false) { + return -1; + } + MODEM.poll(); + + if (_fileUploaded == 0) { + _fileUploaded = -2; + return -1; + }else if (_fileUploaded == 1) { + _fileUploaded = -2; + return 1; + } + return 0; +} + bool GSMFTP::cd(const String& name, uint32_t timeout) { uint32_t start = millis(); @@ -321,12 +413,11 @@ bool GSMFTP::cd(const String& name, uint32_t timeout) return false; } _dirChanged = -2; - String command = "AT+UFTPC=8,\"" + name + "\""; while ((millis() - start) < timeout) { if (_dirChanged == -2) { _dirChanged = -1; - MODEM.send(command); + MODEM.send("AT+UFTPC=8,\"" + name + "\""); if (MODEM.waitForResponse(100) != 1) { return false; } @@ -334,9 +425,7 @@ bool GSMFTP::cd(const String& name, uint32_t timeout) MODEM.poll(); if (_dirChanged == 0) { _dirChanged = -2; - MODEM.send(command); - } - else if (_dirChanged == 1) { + }else if (_dirChanged == 1) { return true; } } @@ -359,128 +448,125 @@ void GSMFTP::printError() Serial.println(res); } -GSMFTP::FTPFileElem GSMFTP::file(uint16_t i) +//--- GSMFTPElem + +GSMFTPElem::Elem GSMFTPElem::elem(uint16_t i) { - if (i < _file.count) { - return _file.e[i]; - } - else { - return FTPFileElem(); + if (i < _count) { + return _elem[i]; + }else { + return Elem(); } } -void GSMFTP::FTPFile::append(const FTPFileElem& elem) +void GSMFTPElem::append(const Elem elem) { - FTPFileElem* tmp = new FTPFileElem[count + 1]; - for (int i = 0; i < count; ++i) { - tmp[i] = e[i]; + Elem* tmp = new Elem[_count + 1]; + for (int i = 0; i < _count; ++i) { + tmp[i] = _elem[i]; } - tmp[count] = elem; - if (e != nullptr) { - delete[] e; + tmp[_count] = elem; + if (_elem != nullptr) { + delete[] _elem; } - e = tmp; - count++; + _elem = tmp; + _count++; } -void GSMFTP::FTPFile::clear() { - if (e != nullptr) { - delete[] e; - e = nullptr; +void GSMFTPElem::clear() { + if (_elem != nullptr) { + delete[] _elem; + _elem = nullptr; } - count = 0; + _count = 0; } -void GSMFTP::FTPFile::show(int i) +void GSMFTPElem::show(int i) { - if (i >= count) { + if (i >= _count) { return; } - Serial.print(e[i].permissions); + Serial.print(_elem[i].permissions); Serial.print(" "); - Serial.print(e[i].number); + Serial.print(_elem[i].number); Serial.print(" "); - Serial.print(e[i].user); + Serial.print(_elem[i].user); Serial.print(" "); - Serial.print(e[i].group); + Serial.print(_elem[i].group); Serial.print(" "); - Serial.print(e[i].size); + Serial.print(_elem[i].size); Serial.print(" "); - Serial.print(e[i].lastModified); + Serial.print(_elem[i].lastModified); Serial.print(" "); - Serial.print(e[i].name); + Serial.print(_elem[i].name); Serial.println(); } -void GSMFTP::FTPFile::parse(const String& str) +void GSMFTPElem::parse(const String& str) { - if (str == "\"") { - ready = true; - } - else { - String res = str; - if (count == 0) { - int i = res.indexOf('"'); - if (i < 0) { - ready = true; - return; - } - res = res.substring(i + 1); + String res = str; + + if (res == "\"") { + return; + }else if (_count == 0) { + int i = res.indexOf('"'); + if (i < 0) { + return; } - FTPFileElem elem; - for (int i = 0; i < 7; ++i) - { - String tmp = res; - int j = res.indexOf(" "); + res = res.substring(i + 1); + } + Elem elem; + for (int i = 0; i < 7; ++i){ + String tmp = res; + int j = res.indexOf(" "); - while (res.charAt(j + 1) == ' ') { - ++j; - } - if (i == 5) { - for (int k = 1; k < 3; ++k) { - j = res.indexOf(" ", j + 1); - while (res.charAt(j + 1) == ' ') { - ++j; - } + while (res.charAt(j + 1) == ' ') { + ++j; + } + if (i == 5) { + for (int k = 1; k < 3; ++k) { + j = res.indexOf(" ", j + 1); + while (res.charAt(j + 1) == ' ') { + ++j; } } + } - if (j > 0) { - tmp = res.substring(0, j + 1); - tmp.trim(); - res = res.substring(j + 1); - } + if (j > 0) { + tmp = res.substring(0, j + 1); + tmp.trim(); + res = res.substring(j + 1); + } - switch (i) - { - case 0: - elem.permissions = tmp; - break; - case 1: - elem.number = tmp.toInt(); - break; - case 2: - elem.user = tmp; - break; - case 3: - elem.group = tmp; - break; - case 4: - elem.size = tmp.toInt(); - break; - case 5: - elem.lastModified = tmp; - break; - case 6: - elem.name = tmp; - break; - default: - break; - } + switch (i) + { + case 0: + elem.permissions = tmp; + break; + case 1: + elem.number = tmp.toInt(); + break; + case 2: + elem.user = tmp; + break; + case 3: + elem.group = tmp; + break; + case 4: + elem.size = tmp.toInt(); + break; + case 5: + elem.lastModified = tmp; + break; + case 6: + elem.name = tmp; + break; + default: + break; } - append(elem); } + append(elem); } void GSMFTP::handleUrc(const String& urc) @@ -492,7 +578,9 @@ void GSMFTP::handleUrc(const String& urc) _connected = false; } else if (urc.startsWith("+UUFTPCD: 13,")) { - ; + if ((urc.charAt(urc.lastIndexOf(",") + 1) == '0')) { + printError(); + } } else if (urc.startsWith("+UUFTPCR: 10,")) { _dirCreated = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; @@ -515,8 +603,11 @@ void GSMFTP::handleUrc(const String& urc) else if (urc.startsWith("+UUFTPCR: 5,")) { _fileUploaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; } + else if (urc.startsWith("+UUFTPCR: 13,")) { + _fileInfo = nullptr; + } - if (_file.ready == false) { - _file.parse(urc); + if (_fileInfo != nullptr) { + _fileInfo->parse(urc); } } diff --git a/src/GSMFTP.h b/src/GSMFTP.h index db3929e..cd7e6f3 100644 --- a/src/GSMFTP.h +++ b/src/GSMFTP.h @@ -22,23 +22,11 @@ #include +class GSMFTPElem; + class GSMFTP : public ModemUrcHandler { public: - struct FTPFileElem - { - String permissions; - uint32_t size; - uint32_t number; - String user; - String group; - String lastModified; - String name; - - FTPFileElem() :size(0), number(0) - {} - }; - GSMFTP(); virtual ~GSMFTP(); @@ -56,20 +44,21 @@ class GSMFTP : public ModemUrcHandler { */ bool disconnect(); /** Get informations of remote directory + @param file class that contains the information of all the files found @param show if true, display information of files @param timeout maximum time allow to execute the function @return true if no error */ - bool ls(bool show=false, uint32_t timeout=10000); - /** Get file number of remote directory - @return number of file read after the call of @ref ls function - */ - uint32_t fileCount() { return _file.count; } - /** Get a file element - @param i index of the file element array to retreive - @return file element - */ - FTPFileElem file(uint16_t i); + bool ls(GSMFTPElem& file, bool show=false, uint32_t timeout=10000); + /** Get informations of remote directory/file + @param file class that contains the information of all the files found + @param name name of file or directory where to search information + @param file name of file or directory where to search information + @param show if true, display information of files + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool ls(GSMFTPElem& file, const String name, bool show = false, uint32_t timeout = 10000); /** Create directory on the FTP server @param name name of the directory to create @param timeout maximum time allow to execute the function @@ -102,46 +91,63 @@ class GSMFTP : public ModemUrcHandler { */ bool cd(const String& path, uint32_t timeout = 10000); /** Download a file from the FTP server - @param remoteFileName name of file on FTP server to retreive on filesystem @param localFileName name of the file on filesystem sent from FTP server + @param remoteFileName name of file on FTP server to retreive on filesystem @param timeout maximum time allow to execute the function @return true if no error - */ - bool download(const String& remoteFileName, const String& localFileName, uint32_t timeout = 10000); - /** Upload a file to the FTP server - @param localFileName name of the file on filesystem to send to FTP server - @param remoteFileName name of the file on FTP server sent from filesystem - @param timeout maximum time allow to execute the function - @return true if no error - */ - bool upload(const String& localFileName, const String&remoteFileName, uint32_t timeout = 10000); - /** Print the error class and code of the last FTP operation - @brief - 0,0 mean no error otherwise {error class},{error code}. - For the description refer to the documention : - https://www.u-blox.com/sites/default/files/u-blox-CEL_ATCommands_%28UBX-13002752%29.pdf + Download a file in blocking mode until the timeout elapsed. */ + bool download(const String& localFileName, const String& remoteFileName, uint32_t timeout = 10000); + /** Start file download from the FTP server + @param localFileName name of the file on filesystem sent from FTP server + @param remoteFileName name of file on FTP server to retreive on filesystem + @return true if no error + + Initialize the file download in non blocking mode. + */ + bool downloadStart(const String& localFileName, const String& remoteFileName); + /** Update download state + @param remoteFileName name of file on FTP server to retreive on filesystem + @param localFileName name of the file on filesystem sent from FTP server + @param showProgression if true show the downloading progression [%] + @return 1 : download finished, 0 downloading, -1 an error occured + + Update the download in non blocking mode. + */ + int downloadReady(const String& localFileName, bool showProgression); + /** Upload a file to the FTP server + @param localFileName name of the file on filesystem to send to FTP server + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool upload(const String& localFileName, const String& remoteFileName, uint32_t timeout = 10000); + /** Start file upload to the FTP server + @param localFileName name of the file on filesystem to send to FTP server + @param remoteFileName name of the file on FTP server sent from filesystem + @return true if no error + + Initialize the file upload in non blocking mode. + */ + bool uploadStart(const String& localFileName, const String& remoteFileName); + /** Update download state + @return 1 : upload finished, 0 downloading, -1 an error occured + + Update the upload in non blocking mode. + When uploading no other request can be send to the server, so we can not display the progress + */ + int uploadReady(); + /** Print the error class and code of the last FTP operation + @brief + 0,0 mean no error otherwise {error class},{error code}. + For the description refer to the documention : + https://www.u-blox.com/sites/default/files/u-blox-CEL_ATCommands_%28UBX-13002752%29.pdf + */ void printError(); private: static const uint32_t c_connectionTimeout = 10000; - struct FTPFile - { - FTPFileElem* e; - uint32_t count; - bool ready; - - void clear(); - void append(const FTPFileElem&); - void show(int); - void parse(const String&); - - FTPFile() :e(nullptr), count(0), ready(true) - {} - }; - void handleUrc(const String&); bool _connected; @@ -152,7 +158,59 @@ class GSMFTP : public ModemUrcHandler { int _fileRenamed; int _fileDownloaded; int _fileUploaded; - FTPFile _file; + GSMFTPElem* _fileInfo; + uint32_t _downloadDisplayTimeRef; + uint32_t _downloadRemoteFileSize; +}; + +class GSMFTPElem +{ +public: + struct Elem + { + String permissions; + uint32_t size; + uint32_t number; + String user; + String group; + String lastModified; + String name; + + Elem() :size(0), number(0) + {} + }; + + GSMFTPElem() :_elem(nullptr), _count(0){} + ~GSMFTPElem() {clear();} + + /** Append a new element in the file array + @param elem elem to append in the array + */ + void append(const Elem elem); + /** Show file information of the corresponding index + @param i index of the element to show + */ + void show(int i); + /** Clear the file array + */ + void clear(); + /** Get a file element + @param i file index to get the element + @return array element relative to the index, empty element if the index is out of range + */ + Elem elem(uint16_t i); + /** Get number of file found + @return number of element in the array + */ + uint32_t count() {return _count;} + /** Parse string containing file information + @param str string containing file information to parse + */ + void parse(const String& str); + +private: + Elem* _elem; + uint32_t _count; }; #endif diff --git a/src/GSMFileSystem.cpp b/src/GSMFileSystem.cpp index 9e4329a..5f5d251 100644 --- a/src/GSMFileSystem.cpp +++ b/src/GSMFileSystem.cpp @@ -20,52 +20,49 @@ #include #include -GSMFileSytem::GSMFileSytem() -{ -} - -GSMFileSytem::~GSMFileSytem() -{ - _file.clear(); -} - -bool GSMFileSytem::ls(bool show, uint32_t timeout) +bool GSMFileSystem::ls(GSMFileSystemElem& file, bool show, uint32_t timeout) { uint32_t start = millis(); - _file.clear(); String response; + file.clear(); MODEM.send("AT+ULSTFILE="); if (MODEM.waitForResponse(timeout, &response) == 1) { - _file.parse(response); + file.parse(response); }else { return false; } - for (int i = 0; i < _file.count; ++i){ + for (int i = 0; i < file.count(); ++i){ if ((millis() - start) > timeout) { - return false; + return false; } - String command = "AT+ULSTFILE=2,\"" + _file.e[i].name + "\""; - MODEM.send(command); + file.setSize(i, size(file.elem(i).name)); - if (MODEM.waitForResponse(1000, &response) == 1) { - if (response.startsWith("+ULSTFILE: ")) { - _file.e[i].size = response.substring(11).toInt(); - } - } if (show == true) { - _file.show(i); + file.show(i); } } - return true; } -bool GSMFileSytem::remove(const String& name) +int32_t GSMFileSystem::size(const String& name) +{ + String response; + + MODEM.send("AT+ULSTFILE=2,\"" + name + "\""); + + if (MODEM.waitForResponse(1000, &response) == 1) { + if (response.startsWith("+ULSTFILE: ")) { + return response.substring(11).toInt(); + } + } + return -1; +} + +bool GSMFileSystem::remove(const String& name) { - String command = "AT+UDELFILE=\"" + name + "\""; - MODEM.send(command); + MODEM.send("AT+UDELFILE=\"" + name + "\""); if (MODEM.waitForResponse(10000) == 1) { return true; }else { @@ -73,7 +70,16 @@ bool GSMFileSytem::remove(const String& name) } } -uint32_t GSMFileSytem::freeSpace() +bool GSMFileSystem::remove(GSMFileSystemElem& file) +{ + bool ok = true; + for (int i = 0; i < file.count(); ++i) { + ok &= remove(file.elem(i).name); + } + return ok; +} + +uint32_t GSMFileSystem::freeSpace() { uint32_t res = 0; String response; @@ -87,10 +93,9 @@ uint32_t GSMFileSytem::freeSpace() return res; } -bool GSMFileSytem::write(const String& fileName, void* data, size_t size) +bool GSMFileSystem::write(const String& fileName, void* data, size_t size) { - String command = "AT+UDWNFILE=\"" + fileName + "\"," + size; - MODEM.send(command); + MODEM.send("AT+UDWNFILE=\"" + fileName + "\"," + size); if (MODEM.waitForPrompt() == 1) { MODEM.write((const uint8_t*)data, size); @@ -101,11 +106,10 @@ bool GSMFileSytem::write(const String& fileName, void* data, size_t size) return false; } -bool GSMFileSytem::read(const String& fileName, void* data, size_t size) +bool GSMFileSystem::read(const String& fileName, void* data, size_t size) { String response; - String command = "AT+URDFILE=\"" + fileName + "\""; - MODEM.send(command); + MODEM.send("AT+URDFILE=\"" + fileName + "\""); if (MODEM.waitForResponse(10000, &response) == 1) { memcpy(data, response.c_str(), size); @@ -114,55 +118,65 @@ bool GSMFileSytem::read(const String& fileName, void* data, size_t size) return false; } -GSMFileSytem::FileElem GSMFileSytem::file(uint16_t i) +//--- GSMFileSystemElem --- + +GSMFileSystemElem::Elem GSMFileSystemElem::elem(uint16_t i) +{ + if (i < _count) { + return _elem[i]; + } + else { + return Elem(); + } +} + +void GSMFileSystemElem::setSize(uint16_t i, uint32_t size) { - if (i < _file.count) { - return _file.e[i]; - }else { - return FileElem(); - } + if (i < _count) { + _elem[i].size = size; + } } -void GSMFileSytem::File::append(const FileElem& elem) +void GSMFileSystemElem::append(const Elem elem) { - FileElem* tmp = new FileElem[count + 1]; - for (int i = 0; i < count; ++i) { - tmp[i] = e[i]; + Elem* tmp = new Elem[_count + 1]; + for (int i = 0; i < _count; ++i) { + tmp[i] = _elem[i]; } - tmp[count] = elem; - if (e != nullptr) { - delete[] e; + tmp[_count] = elem; + if (_elem != nullptr) { + delete[] _elem; } - e = tmp; - count++; + _elem = tmp; + _count++; } -void GSMFileSytem::File::clear() { - if (e != nullptr) { - delete[] e; - e = nullptr; +void GSMFileSystemElem::clear() { + if (_elem != nullptr) { + delete[] _elem; + _elem = nullptr; } - count = 0; + _count = 0; } -void GSMFileSytem::File::show(int i) +void GSMFileSystemElem::show(int i) { - if (i >= count) { + if (i >= _count) { return; } - Serial.print(e[i].name); + Serial.print(_elem[i].name); Serial.print(" "); - Serial.println(e[i].size); + Serial.println(_elem[i].size); } -void GSMFileSytem::File::parse(const String& str) +void GSMFileSystemElem::parse(const String& str) { String res = str; int i = res.indexOf('"')+1; int j = res.indexOf('"', i); while ((i > 0) && (j > 0)){ - FileElem elem; + Elem elem; elem.name = res.substring(i, j); res = res.substring(j + 1); append(elem); @@ -170,3 +184,5 @@ void GSMFileSytem::File::parse(const String& str) j = res.indexOf('"', i); } } + +GSMFileSystem FILESYSTEM; diff --git a/src/GSMFileSystem.h b/src/GSMFileSystem.h index 51a8698..ebc4741 100644 --- a/src/GSMFileSystem.h +++ b/src/GSMFileSystem.h @@ -22,44 +22,38 @@ #include -class GSMFileSytem { +class GSMFileSystemElem; -public: - struct FileElem - { - String name; - uint32_t size; +class GSMFileSystem { - FileElem() :size(0) - {} - }; +public: - GSMFileSytem(); - virtual ~GSMFileSytem(); /** Get name and size of all the file system files @param show if true, display name and size of the files @param timeout maximum time allow to execute the function + @param file class that contains the information of all the files found @return true if no error */ - bool ls(bool show = false, uint32_t timeout = 10000); + bool ls(GSMFileSystemElem& file, bool show = false, uint32_t timeout = 10000); /** Delete a file of the file system @param name name of the file to delete @return true if no error */ bool remove(const String& name); - /** Get file number of the file system - @return number of file read after the call of @ref ls function - */ - uint32_t fileCount() { return _file.count; } - /** Get a file element - @param i index of the file element array to retreive - @return file element + /** Delete files of the file system + @param files set of files to delete + @return true if no error */ - FileElem file(uint16_t i); + bool remove(GSMFileSystemElem& files); /** Get a free space of the file system in bytes @return free space */ uint32_t freeSpace(); + /** Create a file with data in the filesystem + @param name name of the file + @return size of the file, -1 if error + */ + int32_t size(const String& name); /** Create a file with data in the filesystem @param name name of the file to create @param data address of the data to write @@ -75,22 +69,57 @@ class GSMFileSytem { */ bool read(const String& name, void* data, size_t size); -private: - struct File - { - FileElem* e; - uint32_t count; +}; + +class GSMFileSystemElem { - void clear(); - void append(const FileElem&); - void show(int); - void parse(const String&); +public: + struct Elem + { + String name; + uint32_t size; - File() :e(nullptr), count(0) - {} - }; + Elem() :size(0) + {} + }; - File _file; + GSMFileSystemElem() :_elem(nullptr), _count(0){} + ~GSMFileSystemElem() { clear(); } + /** Get file number of the file system + @return number of file read after the call of @ref ls function +*/ + inline uint32_t count() { return _count; } + /** Get a file element + @param i file index to get the element + @return file element + */ + Elem elem(uint16_t i); + /** Get file size + @param i file index to get the size + @return file size + */ + void setSize(uint16_t i, uint32_t size); + /** Clear the file array + */ + void clear(); + /** Append a new element in the file array + @param elem elem to append in the array + */ + void append(const Elem elem); + /** Show file information of the corresponding index + @param i index of the element to show + */ + void show(int i); + /** Parse string containing file information + @param str string containing file information to parse + */ + void parse(const String& str); + +private: + Elem* _elem; + uint32_t _count; }; +extern GSMFileSystem FILESYSTEM; + #endif From 5dde41998864c73f7799eefe9fa4ebf5643f42b1 Mon Sep 17 00:00:00 2001 From: tryhus Date: Tue, 15 Jan 2019 23:58:58 +0100 Subject: [PATCH 4/5] Add direct link mode for FTP upload and download - Direct link mode allow to transfer between Arduino SRAM and FTP server - Simplify some function and example ino file --- examples/FTP/FTP.ino | 318 ++++++++------- src/GSMFTP.cpp | 914 +++++++++++++++++++++++-------------------- src/GSMFTP.h | 272 +++++++------ src/Modem.cpp | 32 +- src/Modem.h | 2 + 5 files changed, 818 insertions(+), 720 deletions(-) diff --git a/examples/FTP/FTP.ino b/examples/FTP/FTP.ino index 554ab0f..f8e9d7e 100644 --- a/examples/FTP/FTP.ino +++ b/examples/FTP/FTP.ino @@ -1,7 +1,7 @@ /* FTP client - This sketch connects to a FTP server through a MKR GSM 1400 board. + This sketch connects to a FTP server through a MKR GSM 1400 board. Circuit: * MKR GSM 1400 board @@ -20,14 +20,14 @@ // Please enter your sensitive data in the Secret tab or arduino_secrets.h // PIN Number -const char PINNUMBER[] = SECRET_PINNUMBER; +const char PINNUMBER[] = SECRET_PINNUMBER; // APN data -const char GPRS_APN[] = SECRET_GPRS_APN; -const char GPRS_LOGIN[] = SECRET_GPRS_LOGIN; +const char GPRS_APN[] = SECRET_GPRS_APN; +const char GPRS_LOGIN[] = SECRET_GPRS_LOGIN; const char GPRS_PASSWORD[] = SECRET_GPRS_PASSWORD; //this file must be present in the remote directory SECRET_FTP_REMOTE_DIR -const String c_downloadFileName = "downloadFile"; +const String c_downloadFileName = "downloadFile"; // initialize the library instance GSMFTP ftp; @@ -49,9 +49,10 @@ void setup() { // attach the shield to the GPRS network with the APN, login and password while (!connected) { if ((gsmAccess.begin(PINNUMBER) == GSM_READY) && - (gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) { + (gprs.attachGPRS(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD) == GPRS_READY)) { connected = true; - } else { + } + else { Serial.println("Not connected"); delay(1000); } @@ -60,167 +61,156 @@ void setup() { void loop() { GSMFileSystemElem localFile; - GSMFTPElem remoteFile; - - Serial.println("Connect to FTP server."); - if (ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD, SECRET_FTP_PORT) == false) { - Serial.println("Failed to Connect to FTP server."); - ftp.printError(); - } - - Serial.println("Change of directory"); - if (ftp.cd(SECRET_FTP_REMOTE_DIR) == false) { - Serial.println("Failed to change of directory."); - } - - Serial.print("Free space "); - Serial.println(FILESYSTEM.freeSpace()); - - Serial.println("Create remote directory : test"); - if (ftp.mkdir("test") == false) { - Serial.println("Failed to create the directory."); - } - - Serial.println("Rename remote directory : test to test2"); - if (ftp.rename("test", "test2") == false) { - Serial.println("Failed to rename the directory."); - } - - Serial.println("Write a binary file in local memory"); - double valueWR = -12.5789876; - double valueRD = 0; - if (FILESYSTEM.write("myFile", &valueWR, sizeof(valueWR)) == false) { - Serial.println("Failed to write file"); - } - - Serial.println("Send the file to the server"); - if (ftp.upload("myFile", "myFileToServer") == false) { - Serial.println("Failed to upload the file."); - ftp.printError(); - } - - Serial.println("Retreive the file from the server to local memory"); - if (ftp.download("myFileToLocalMemory", "myFileToServer") == false) { - Serial.println("Failed to download the file."); - ftp.printError(); - } - - Serial.println("Check that the original file is identical to the one that was received"); - if (FILESYSTEM.read("myFileToLocalMemory", &valueRD, sizeof(valueRD)) == false) { - Serial.println("Failed to read file"); - } - else if (valueWR != valueRD) { - Serial.println("Failed to read file, value is corrupted"); - } - - Serial.print("Free space "); - Serial.println(FILESYSTEM.freeSpace()); - - Serial.println("Display local files"); - if (FILESYSTEM.ls(localFile, true) == false) { - Serial.println("Failed to display local files"); - } - - Serial.println("Remove local files"); - if (FILESYSTEM.remove(localFile) == false) { - Serial.println("Failed to remove file"); - } - - Serial.println("Display local files"); - if (FILESYSTEM.ls(localFile, true) == false) { - Serial.println("Failed to display local files"); - } - - Serial.println("Display remote files"); - if (ftp.ls(remoteFile, true) == false) { - Serial.println("Failed to display files."); - } - - Serial.println("Delete the created file and directory"); - if (ftp.removeDirectory("test2") == false) { - Serial.println("Failed to remove files : test2."); - } - if (ftp.removeFile("myFileToServer") == false) { - Serial.println("Failed to remove files : myFileToServer."); - } - - Serial.println("Display remote files"); - if (ftp.ls(remoteFile, true) == false) { - Serial.println("Failed to display files."); - } - - //--- Test download/upload a large file with non blocking function --- - - Serial.println(); - Serial.println("Download a file with non blocking function"); - downloadFileNonBlocking("downloadedFile", c_downloadFileName); - - Serial.println("Display local files"); - if (FILESYSTEM.ls(localFile, true) == false) { - Serial.println("Failed to display local files"); - } - - Serial.println("Upload a file with non blocking function"); - uploadFileNonBlocking("downloadedFile", "uploadFile"); - - Serial.println("Display remote files"); - if (ftp.ls(remoteFile, true) == false) { - Serial.println("Failed to display files."); - } - - Serial.println("Remove local and remote files"); - if (FILESYSTEM.remove("downloadedFile") == false) { - Serial.println("Failed to remove file"); - } - if (ftp.removeFile("uploadFile") == false) { - Serial.println("Failed to remove files : myFileToServer."); - } - - Serial.println("Disconnect to FTP server"); - if (ftp.disconnect() == false) { - Serial.println("Failed to disconnect."); - } - - for (;;) - ; + GSMFTPElem remoteFile; + + test("Connect to FTP server", + ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD, SECRET_FTP_PORT)); + + test("Change current remote directory", + ftp.cd(SECRET_FTP_REMOTE_DIR)); + + test("Create remote directory", + ftp.mkdir("test")); + + test("Rename remote directory", + ftp.rename("test", "test2")); + + double valueWR = -12.5789876; + double valueRD = 0; + + test("Local file system write", + FILESYSTEM.write("myFile", &valueWR, sizeof(valueWR))); + + test("Local file system free space", + (FILESYSTEM.freeSpace() > 0)); + + test("Upload from local file system to FTP server", + ftp.upload("myFile", "myFileToServer", 10000)); + + test("Download from FTP server to local file system", + ftp.download("myFileToLocalMemory", "myFileToServer", 10000)); + + test("Local file system read", + FILESYSTEM.read("myFileToLocalMemory", &valueRD, sizeof(valueRD))); + + test("Check local file consistency after upload, download then read local file system", + (valueRD == valueWR)); + + test("Display local files", + FILESYSTEM.ls(localFile, true)); + + test("Remove local files", + FILESYSTEM.remove(localFile)); + + test("Display local files", + FILESYSTEM.ls(localFile, true)); + + test("Display remote files", + ftp.ls(remoteFile, true)); + + test("Delete remote directory", + ftp.removeDirectory("test2")); + + test("Delete remote file", + ftp.removeFile("myFileToServer")); + + test("Display remote files", + ftp.ls(remoteFile, true)); + + //--- Test download/upload a large file with non blocking function --- + + test("Non blocking download from FTP server to local file system", + downloadFileNonBlocking("downloadedFile", c_downloadFileName)); + + test("Display local files", + FILESYSTEM.ls(localFile, true)); + + test("Non blocking upload from local file system to FTP server", + uploadFileNonBlocking("downloadedFile", "uploadFile")); + + test("Display local files", + FILESYSTEM.remove("downloadedFile")); + + test("Delete remote file", + ftp.removeFile("uploadFile")); + + //--- Test direct upload/download --- + //direct transfer doesn't use local file system but volatile memory + //upload volatile memory => FTP server + //download FTP server => volatile memory + + String fileName = "myFile.txt"; + char bufferWR[128]; + char bufferRD[128]; + for (int i = 0; i < 128; ++i) { + bufferWR[i] = 33 + i; + bufferRD[i] = 0; + } + + test("Direct upload from volatile memory to FTP server", + ftp.write(&bufferWR[0], sizeof(bufferWR), fileName, 10000)); + + test("Direct download from FTP server to volatile memory", + ftp.read(&bufferRD[0], sizeof(bufferRD), fileName, 10000)); + + test("Direct upload/download tranferred data consistency", + (memcmp(bufferRD, bufferWR, 128) == 0)); + + test("Delete remote file", + ftp.removeFile(fileName)); + + test("Disconnect to FTP server", + ftp.disconnect()); + + for (;;) + ; } //Example of non blocking download functions -void downloadFileNonBlocking(const String localFileName, const String remoteFileName) { - - Serial.println("Retreive the file from the server to local memory"); - //Start download - if (ftp.downloadStart(localFileName, remoteFileName) == false) { - Serial.println("Failed to start download."); - ftp.printError(); - } - - //update download - while (ftp.downloadReady(localFileName, true) == 0) - { - //do some job - } +bool downloadFileNonBlocking(const String localFileName, const String remoteFileName) +{ + int res = 0; + + //Start download + if (ftp.downloadStart(localFileName, remoteFileName) == false) { + return false; + } + + //update download + while (res == 0) { + res = ftp.downloadReady(localFileName, true); + //do some job + } + + return (res == 1); } //Example of non blocking upload functions -void uploadFileNonBlocking(const String localFileName, const String remoteFileName) { - - Serial.println("Send the file to the server from local memory"); - if (ftp.uploadStart(localFileName, remoteFileName) == false) { - Serial.println("Failed to start upload."); - ftp.printError(); - } - - int res = 0; - while (res == 0){ - res = ftp.uploadReady(); - if (res == 1) { - Serial.println("Upload finished."); - } - else if (res < 0) { - Serial.println("Upload error."); - ftp.printError(); - } - //do some job - } +bool uploadFileNonBlocking(const String localFileName, const String remoteFileName) +{ + int res = 0; + + if (ftp.uploadStart(localFileName, remoteFileName) == false) { + return false; + } + + while (res == 0) { + res = ftp.uploadReady(); + //do some job + } + + return (res == 1); +} + +bool test(const String& msg, bool function) +{ + if (function == true) { + Serial.print("OK - "); + } + else { + Serial.print("ERROR - "); + } + Serial.println(msg); + + return function; } diff --git a/src/GSMFTP.cpp b/src/GSMFTP.cpp index 768d8e5..98d8d28 100644 --- a/src/GSMFTP.cpp +++ b/src/GSMFTP.cpp @@ -20,16 +20,20 @@ #include #include -GSMFTP::GSMFTP(): - _connected(false), - _dirCreated(-1), - _dirChanged(-1), - _fileRemoved(-1), - _downloadDisplayTimeRef(0), - _downloadRemoteFileSize(0), - _fileInfo(nullptr) +GSMFTP::GSMFTP() : + _connected(-1), + _dirCreated(-1), + _dirChanged(-1), + _fileRemoved(-1), + _fileDownloaded(-1), + _fileUploaded(-1), + _fileDirectUploaded(-1), + _fileDirectDownloaded(-1), + _downloadDisplayTimeRef(0), + _downloadRemoteFileSize(0), + _fileInfo(nullptr) { - MODEM.addUrcHandler(this); + MODEM.addUrcHandler(this); } GSMFTP::~GSMFTP() @@ -39,117 +43,108 @@ GSMFTP::~GSMFTP() bool GSMFTP::connect(String hostname, String user, String password, uint16_t port, bool passiveMode) { - uint32_t start = millis(); - - MODEM.send("AT+UFTP=1,\"" + hostname + "\""); - if (MODEM.waitForResponse(100) != 1){ - return false; - } - - MODEM.send("AT+UFTP=2,\"" + user + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - - MODEM.send("AT+UFTP=3,\"" + password + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - - String command = (passiveMode == true) ? "AT+UFTP=6,1" : "AT+UFTP=6,0"; - MODEM.send(command); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - - MODEM.sendf("AT+UFTP=7,%d", port); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - - MODEM.send("AT+UDNSRN=0,\"" + hostname + "\""); - if (MODEM.waitForResponse(10000) != 1) { - return false; - } - - while ((_connected == false) && (millis() - start) < c_connectionTimeout) { - MODEM.send("AT+UFTPC=1"); - if (MODEM.waitForResponse(100) != 1) { - continue; - } - for (int i = 0; i < 30; ++i) { - delay(100); - MODEM.ready(); - if (_connected == true){ - break; - } - } - } - - return _connected; + uint32_t start = millis(); + + MODEM.send("AT+UFTP=1,\"" + hostname + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + MODEM.send("AT+UFTP=2,\"" + user + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + MODEM.send("AT+UFTP=3,\"" + password + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + String command = (passiveMode == true) ? "AT+UFTP=6,1" : "AT+UFTP=6,0"; + MODEM.send(command); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + MODEM.sendf("AT+UFTP=7,%d", port); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + MODEM.send("AT+UDNSRN=0,\"" + hostname + "\""); + if (MODEM.waitForResponse(10000) != 1) { + return false; + } + + _connected = -2; + while ((_connected != 1) && (millis() - start) < c_connectionTimeout) { + if (_connected == -2) { + MODEM.send("AT+UFTPC=1"); + _connected = -1; + } + else if (_connected == 0) { + _connected = -2; + } + MODEM.poll(); + } + + return (_connected == 1); } bool GSMFTP::disconnect() { - uint32_t start = millis(); - while ((_connected == true) && (millis() - start) < 10000) { - MODEM.send("AT+UFTPC=0"); - if (MODEM.waitForResponse(100) != 1) { - continue; - } - for (int i = 0; i < 30; ++i) { - delay(100); - MODEM.ready(); - if (_connected == false) { - break; - } - } - } - - return !_connected; + uint32_t start = millis(); + _connected = -1; + + MODEM.send("AT+UFTPC=0"); + + while ((_connected == -1) && (millis() - start) < 10000) { + MODEM.poll(); + } + return (_connected == 0); } bool GSMFTP::ls(GSMFTPElem& file, bool show, uint32_t timeout) { - if (_connected == true) { - file.clear(); - _fileInfo = &file; - uint32_t start = millis(); - - MODEM.send("AT+UFTPC=13"); - - while (_fileInfo != nullptr) { - MODEM.poll(); - if ((millis() - start) > timeout) { - _fileInfo = nullptr; - return false; - } - } - - if (show == true) { - for (int i = 0; i < file.count(); ++i) { - file.show(i); - } - } - return true; - } - else{ - return false; - } + if (_connected == 1) { + file.clear(); + _fileInfo = &file; + uint32_t start = millis(); + + MODEM.send("AT+UFTPC=13"); + + while (_fileInfo != nullptr) { + MODEM.poll(); + if ((millis() - start) > timeout) { + _fileInfo = nullptr; + return false; + } + } + + if (show == true) { + for (int i = 0; i < file.count(); ++i) { + file.show(i); + } + } + return true; + } + else { + return false; + } } bool GSMFTP::ls(GSMFTPElem& file, const String name, bool show, uint32_t timeout) { - if (_connected == true) { + if (_connected == 1) { file.clear(); - _fileInfo = &file; + _fileInfo = &file; uint32_t start = millis(); MODEM.send("AT+UFTPC = 13,\"" + name + "\""); while (_fileInfo != nullptr) { MODEM.poll(); if ((millis() - start) > timeout) { - _fileInfo = nullptr; + _fileInfo = nullptr; return false; } } @@ -168,146 +163,127 @@ bool GSMFTP::ls(GSMFTPElem& file, const String name, bool show, uint32_t timeout bool GSMFTP::mkdir(const String& name, uint32_t timeout) { - uint32_t start = millis(); - if (_connected == false){ - return false; - } - _dirCreated = -2; - - while ((millis() - start) < timeout) { - if (_dirCreated == -2) { - _dirCreated = -1; - MODEM.send("AT+UFTPC=10,\"" + name + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_dirCreated == 0) { - _dirCreated = -2; - }else if (_dirCreated == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + _dirCreated = -1; + MODEM.send("AT+UFTPC=10,\"" + name + "\""); + + while ((millis() - start) < timeout) { + MODEM.poll(); + if (_dirCreated == 0) { + return false; + } + else if (_dirCreated == 1) { + return true; + } + } + return false; } bool GSMFTP::removeFile(const String& name, uint32_t timeout) { - uint32_t start = millis(); - if (_connected == false) { - return false; - } - - _fileRemoved = -2; - - while ((millis() - start) < timeout) { - if (_fileRemoved == -2) { - _fileRemoved = -1; - MODEM.send("AT+UFTPC=2,\"" + name + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_fileRemoved == 0) { - _fileRemoved = -2; - }else if (_fileRemoved == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + _fileRemoved = -1; + MODEM.send("AT+UFTPC=2,\"" + name + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + + while ((millis() - start) < timeout) { + MODEM.poll(); + if (_fileRemoved == 0) { + _fileRemoved = -1; + return false; + } + else if (_fileRemoved == 1) { + return true; + } + } + return false; } bool GSMFTP::removeDirectory(const String& name, uint32_t timeout) { - uint32_t start = millis(); - if (_connected == false) { - return false; - } - - _dirRemoved = -2; - - while ((millis() - start) < timeout) { - if (_dirRemoved == -2) { - _dirRemoved = -1; - MODEM.send("AT+UFTPC=11,\"" + name + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_dirRemoved == 0) { - _dirRemoved = -2; - }else if (_dirRemoved == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + _dirRemoved = -1; + MODEM.send("AT+UFTPC=11,\"" + name + "\""); + + + while ((millis() - start) < timeout) { + MODEM.poll(); + if (_dirRemoved == 0) { + return false; + } + else if (_dirRemoved == 1) { + return true; + } + } + return false; } bool GSMFTP::rename(const String& oldName, const String& name, uint32_t timeout) { - uint32_t start = millis(); - if (_connected == false) { - return false; - } - _fileRenamed= -2; - - while ((millis() - start) < timeout) { - if (_fileRenamed == -2) { - _fileRenamed = -1; - MODEM.send("AT+UFTPC=3,\"" + oldName + "\",\"" + name + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_fileRenamed == 0) { - _fileRenamed = -2; - }else if (_fileRenamed == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + _fileRenamed = -1; + MODEM.send("AT+UFTPC=3,\"" + oldName + "\",\"" + name + "\""); + + while ((millis() - start) < timeout) { + MODEM.poll(); + if (_fileRenamed == 0) { + return false; + } + else if (_fileRenamed == 1) { + return true; + } + } + return false; } -bool GSMFTP::download(const String& localFileName, const String& remoteFileName, uint32_t timeout) +bool GSMFTP::download(const String& localFileName, const String& remoteFileName, int32_t timeout) { - uint32_t start = millis(); - if (_connected == false) { - return false; - } - _fileDownloaded = -2; - - while ((millis() - start) < timeout) { - if (_fileDownloaded == -2) { - _fileDownloaded = -1; - MODEM.send("AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_fileDownloaded == 0) { - _fileDownloaded = -2; - }else if (_fileDownloaded == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + _fileDownloaded = -1; + MODEM.send("AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""); + + while ((timeout < 0) || (millis() - start) < timeout) { + MODEM.poll(); + if (_fileDownloaded == 0) { + return false; + } + else if (_fileDownloaded == 1) { + return true; + } + } + return false; } bool GSMFTP::downloadStart(const String& localFileName, const String& remoteFileName) { - if (_connected == false) { + if (_connected != 1) { return false; } //get remote file informations - GSMFTPElem remoteFile; + GSMFTPElem remoteFile; ls(remoteFile, remoteFileName); - _downloadRemoteFileSize = remoteFile.elem(0).size; - _fileDownloaded = -2; + _downloadRemoteFileSize = remoteFile.elem(0).size; + _fileDownloaded = -1; MODEM.send("AT+UFTPC=4,\"" + remoteFileName + "\",\"" + localFileName + "\""); if (MODEM.waitForResponse(100) != 1) { @@ -318,70 +294,68 @@ bool GSMFTP::downloadStart(const String& localFileName, const String& remoteFile int GSMFTP::downloadReady(const String& localFileName, bool showProgression) { - if (_connected == false) { + if (_connected != 1) { return -1; } + MODEM.poll(); if (_fileDownloaded == 0) { - _fileDownloaded = -2; if (showProgression == true) { Serial.println("Failed to download the file."); } return -1; - }else if (_fileDownloaded == 1) { - _fileDownloaded = -2; + } + else if (_fileDownloaded == 1) { + _fileDownloaded = -1; if (showProgression == true) { Serial.println("Download 100%"); } return 1; - }else if ((showProgression == true) && - ((millis() - _downloadDisplayTimeRef) > 5000)) { - double progress = 0; - if (_downloadRemoteFileSize > 0) { - progress = 100.0*FILESYSTEM.size(localFileName) / (double)(_downloadRemoteFileSize); - } - Serial.print("Download "); - Serial.print(progress); - Serial.println(" %"); - _downloadDisplayTimeRef = millis(); - } - return 0; + } + else if ((showProgression == true) && + ((millis() - _downloadDisplayTimeRef) > 5000)) { + double progress = 0; + if (_downloadRemoteFileSize > 0) { + progress = 100.0*FILESYSTEM.size(localFileName) / (double)(_downloadRemoteFileSize); + } + Serial.print("Download "); + Serial.print(progress); + Serial.println(" %"); + _downloadDisplayTimeRef = millis(); + } + return 0; } -bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, uint32_t timeout) +bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, int32_t timeout) { - uint32_t start = millis(); - if (_connected == false) { - return false; - } - _fileUploaded = -2; - - while ((millis() - start) < timeout) { - if (_fileUploaded == -2) { - _fileUploaded = -1; - MODEM.send("AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_fileUploaded == 0) { - _fileUploaded = -2; - }else if (_fileUploaded == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + _fileUploaded = -1; + MODEM.send("AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""); + + while ((timeout < 0) || (millis() - start) < timeout) { + MODEM.poll(); + if (_fileUploaded == 0) { + return false; + } + else if (_fileUploaded == 1) { + return true; + } + } + return false; } bool GSMFTP::uploadStart(const String& localFileName, const String& remoteFileName) { - if (_connected == false) { + if (_connected != 1) { return false; } - _fileUploaded = -2; + _fileUploaded = -1; MODEM.send("AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""); if (MODEM.waitForResponse(100) != 1) { return false; @@ -391,223 +365,301 @@ bool GSMFTP::uploadStart(const String& localFileName, const String& remoteFileNa int GSMFTP::uploadReady() { - if (_connected == false) { + if (_connected != 1) { return -1; } + MODEM.poll(); if (_fileUploaded == 0) { - _fileUploaded = -2; return -1; - }else if (_fileUploaded == 1) { - _fileUploaded = -2; - return 1; - } + } + else if (_fileUploaded == 1) { + _fileUploaded = -2; + return 1; + } return 0; } bool GSMFTP::cd(const String& name, uint32_t timeout) { - uint32_t start = millis(); - if (_connected == false) { - return false; - } - _dirChanged = -2; - - while ((millis() - start) < timeout) { - if (_dirChanged == -2) { - _dirChanged = -1; - MODEM.send("AT+UFTPC=8,\"" + name + "\""); - if (MODEM.waitForResponse(100) != 1) { - return false; - } - } - MODEM.poll(); - if (_dirChanged == 0) { - _dirChanged = -2; - }else if (_dirChanged == 1) { - return true; - } - } - return false; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + _dirChanged = -1; + MODEM.send("AT+UFTPC=8,\"" + name + "\""); + + while ((millis() - start) < timeout) { + MODEM.poll(); + if (_dirChanged == 0) { + return false; + } + else if (_dirChanged == 1) { + return true; + } + } + return false; } void GSMFTP::printError() { - String res = "FTP last error : "; - String response; - - MODEM.send("AT+UFTPER"); - - if ((MODEM.waitForResponse(1000, &response) == 1) && - (response.startsWith("+UFTPER:"))) { - res += response.substring(8); - }else { - res += "no response"; - } - Serial.println(res); + String res = "FTP last error : "; + String response; + + MODEM.send("AT+UFTPER"); + + if ((MODEM.waitForResponse(1000, &response) == 1) && + (response.startsWith("+UFTPER:"))) { + res += response.substring(8); + } + else { + res += "no response"; + } + Serial.println(res); +} + +bool GSMFTP::write(void* data, size_t size, const String& remoteFileName, int32_t timeout) +{ + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + _fileDirectUploaded = -2; + + MODEM.send("AT+UFTPC=7,\"" + remoteFileName + "\""); + String resp; + + if (MODEM.waitForResponse(timeout, &resp) != 1) { + return false; + } + + MODEM.write((const uint8_t*)data, size); + MODEM.escapeSequence(1500, 0); + + while ((timeout < 0) || ((millis() - start) < timeout)) { + + MODEM.poll(); + + if (_fileDirectUploaded == 0) { + return false; + } + else if (_fileDirectUploaded == 1) { + return true; + } + } + + return false; +} + +bool GSMFTP::read(void* data, size_t size, const String& remoteFileName, int32_t timeout) +{ + _fileDirectDownloaded = -1; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + GSMFTPElem remoteFile; + ls(remoteFile, remoteFileName); + if (remoteFile.elem(0).size == 0) { + return false; + } + else if (size > remoteFile.elem(0).size) { + size = remoteFile.elem(0).size; + } + + MODEM.send("AT+UFTPC=6,\"" + remoteFileName + "\""); + + if (MODEM.waitForResponse(timeout) != 1) { + return false; + } + uint32_t res = MODEM.read((uint8_t*)data, size, timeout); + + if (res < remoteFile.elem(0).size) { + MODEM.escapeSequence(1000, 1000, true); + } + + while (((timeout < 0) || (millis() - start) < timeout)) { + + MODEM.poll(); + + if ((_fileDirectDownloaded == 0) || (_fileDirectDownloaded == 1)) { + return (res == size); + } + } + return false; } //--- GSMFTPElem GSMFTPElem::Elem GSMFTPElem::elem(uint16_t i) { - if (i < _count) { - return _elem[i]; - }else { - return Elem(); - } + if (i < _count) { + return _elem[i]; + } + else { + return Elem(); + } } void GSMFTPElem::append(const Elem elem) { - Elem* tmp = new Elem[_count + 1]; - for (int i = 0; i < _count; ++i) { - tmp[i] = _elem[i]; - } - tmp[_count] = elem; - if (_elem != nullptr) { - delete[] _elem; - } - _elem = tmp; - _count++; + Elem* tmp = new Elem[_count + 1]; + for (int i = 0; i < _count; ++i) { + tmp[i] = _elem[i]; + } + tmp[_count] = elem; + if (_elem != nullptr) { + delete[] _elem; + } + _elem = tmp; + _count++; } void GSMFTPElem::clear() { - if (_elem != nullptr) { - delete[] _elem; - _elem = nullptr; - } - _count = 0; + if (_elem != nullptr) { + delete[] _elem; + _elem = nullptr; + } + _count = 0; } void GSMFTPElem::show(int i) { - if (i >= _count) { - return; - } - - Serial.print(_elem[i].permissions); - Serial.print(" "); - Serial.print(_elem[i].number); - Serial.print(" "); - Serial.print(_elem[i].user); - Serial.print(" "); - Serial.print(_elem[i].group); - Serial.print(" "); - Serial.print(_elem[i].size); - Serial.print(" "); - Serial.print(_elem[i].lastModified); - Serial.print(" "); - Serial.print(_elem[i].name); - Serial.println(); + if (i >= _count) { + return; + } + + Serial.print(_elem[i].permissions); + Serial.print(" "); + Serial.print(_elem[i].number); + Serial.print(" "); + Serial.print(_elem[i].user); + Serial.print(" "); + Serial.print(_elem[i].group); + Serial.print(" "); + Serial.print(_elem[i].size); + Serial.print(" "); + Serial.print(_elem[i].lastModified); + Serial.print(" "); + Serial.print(_elem[i].name); + Serial.println(); } void GSMFTPElem::parse(const String& str) { - String res = str; - - if (res == "\"") { - return; - }else if (_count == 0) { - int i = res.indexOf('"'); - if (i < 0) { - return; - } - res = res.substring(i + 1); - } - Elem elem; - for (int i = 0; i < 7; ++i){ - String tmp = res; - int j = res.indexOf(" "); - - while (res.charAt(j + 1) == ' ') { - ++j; - } - if (i == 5) { - for (int k = 1; k < 3; ++k) { - j = res.indexOf(" ", j + 1); - while (res.charAt(j + 1) == ' ') { - ++j; - } - } - } - - if (j > 0) { - tmp = res.substring(0, j + 1); - tmp.trim(); - res = res.substring(j + 1); - } - - switch (i) - { - case 0: - elem.permissions = tmp; - break; - case 1: - elem.number = tmp.toInt(); - break; - case 2: - elem.user = tmp; - break; - case 3: - elem.group = tmp; - break; - case 4: - elem.size = tmp.toInt(); - break; - case 5: - elem.lastModified = tmp; - break; - case 6: - elem.name = tmp; - break; - default: - break; - } - } - append(elem); + String res = str; + + if (res == "\"") { + return; + } + else if (_count == 0) { + int i = res.indexOf('"'); + if (i < 0) { + return; + } + res = res.substring(i + 1); + } + Elem elem; + for (int i = 0; i < 7; ++i) { + String tmp = res; + int j = res.indexOf(" "); + + while (res.charAt(j + 1) == ' ') { + ++j; + } + if (i == 5) { + for (int k = 1; k < 3; ++k) { + j = res.indexOf(" ", j + 1); + while (res.charAt(j + 1) == ' ') { + ++j; + } + } + } + + if (j > 0) { + tmp = res.substring(0, j + 1); + tmp.trim(); + res = res.substring(j + 1); + } + + switch (i) + { + case 0: + elem.permissions = tmp; + break; + case 1: + elem.number = tmp.toInt(); + break; + case 2: + elem.user = tmp; + break; + case 3: + elem.group = tmp; + break; + case 4: + elem.size = tmp.toInt(); + break; + case 5: + elem.lastModified = tmp; + break; + case 6: + elem.name = tmp; + break; + default: + break; + } + } + append(elem); } void GSMFTP::handleUrc(const String& urc) { - if (urc.startsWith("+UUFTPCR: 1,1")) { - _connected = true; - } - else if (urc.startsWith("+UUFTPCR: 0,1")) { - _connected = false; - } - else if (urc.startsWith("+UUFTPCD: 13,")) { + if (urc.startsWith("+UUFTPCR: 1,")) { + _connected = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : -1; + } + else if (urc.startsWith("+UUFTPCR: 0,")) { + _connected = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 0 : -1; + } + else if (urc.startsWith("+UUFTPCD: 13,")) { if ((urc.charAt(urc.lastIndexOf(",") + 1) == '0')) { printError(); } - } - else if (urc.startsWith("+UUFTPCR: 10,")) { - _dirCreated = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } - else if (urc.startsWith("+UUFTPCR: 8,")) { - _dirChanged = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } - else if (urc.startsWith("+UUFTPCR: 2,")) { - _fileRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } - else if (urc.startsWith("+UUFTPCR: 11,")) { - _dirRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } - else if (urc.startsWith("+UUFTPCR: 3,")) { - _fileRenamed = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } - else if (urc.startsWith("+UUFTPCR: 4,")) { - _fileDownloaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } - else if (urc.startsWith("+UUFTPCR: 5,")) { - _fileUploaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; - } + } + else if (urc.startsWith("+UUFTPCR: 10,")) { + _dirCreated = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 8,")) { + _dirChanged = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 2,")) { + _fileRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 11,")) { + _dirRemoved = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 3,")) { + _fileRenamed = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 4,")) { + _fileDownloaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 5,")) { + _fileUploaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 6,")) { + _fileDirectDownloaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } + else if (urc.startsWith("+UUFTPCR: 7,")) { + _fileDirectUploaded = (urc.charAt(urc.lastIndexOf(",") + 1) == '1') ? 1 : 0; + } else if (urc.startsWith("+UUFTPCR: 13,")) { _fileInfo = nullptr; } - if (_fileInfo != nullptr) { - _fileInfo->parse(urc); - } + if (_fileInfo != nullptr) { + _fileInfo->parse(urc); + } } diff --git a/src/GSMFTP.h b/src/GSMFTP.h index cd7e6f3..ae29213 100644 --- a/src/GSMFTP.h +++ b/src/GSMFTP.h @@ -30,84 +30,84 @@ class GSMFTP : public ModemUrcHandler { GSMFTP(); virtual ~GSMFTP(); - /** Connect to a FTP server - @param hostname FTP server hostname - @param user FTP user name - @param password FTP password - @param port FTP server port - @param passiveMode true if passive mode is active - @return true if no error - */ - bool connect(String hostname, String user, String password, uint16_t port, bool passiveMode=true); - /** Disconnect to the FTP server - @return true if no error - */ - bool disconnect(); - /** Get informations of remote directory - @param file class that contains the information of all the files found - @param show if true, display information of files - @param timeout maximum time allow to execute the function - @return true if no error + /** Connect to a FTP server + @param hostname FTP server hostname + @param user FTP user name + @param password FTP password + @param port FTP server port + @param passiveMode true if passive mode is active + @return true if no error + */ + bool connect(String hostname, String user, String password, uint16_t port, bool passiveMode = true); + /** Disconnect to the FTP server + @return true if no error + */ + bool disconnect(); + /** Get informations of remote directory + @param file class that contains the information of all the files found + @param show if true, display information of files + @param timeout maximum time allow to execute the function + @return true if no error */ - bool ls(GSMFTPElem& file, bool show=false, uint32_t timeout=10000); + bool ls(GSMFTPElem& file, bool show = false, uint32_t timeout = 10000); /** Get informations of remote directory/file - @param file class that contains the information of all the files found + @param file class that contains the information of all the files found @param name name of file or directory where to search information - @param file name of file or directory where to search information - @param show if true, display information of files + @param file name of file or directory where to search information + @param show if true, display information of files @param timeout maximum time allow to execute the function @return true if no error */ bool ls(GSMFTPElem& file, const String name, bool show = false, uint32_t timeout = 10000); - /** Create directory on the FTP server - @param name name of the directory to create - @param timeout maximum time allow to execute the function - @return true if no error - */ - bool mkdir(const String& name, uint32_t timeout=10000); - /** Delete directory on the FTP server - @param name name of the directory to delete - @param timeout maximum time allow to execute the function - @return true if no error - */ - bool removeDirectory(const String&, uint32_t timeout = 10000); - /** Delete file on the FTP server - @param name name of the file to delete - @param timeout maximum time allow to execute the function - @return true if no error - */ - bool removeFile(const String&, uint32_t timeout = 10000); - /** Rename file on the FTP server - @param oldName name of the file to rename - @param name new name of the file to rename - @param timeout maximum time allow to execute the function - @return true if no error - */ - bool rename(const String& oldName, const String& name, uint32_t timeout = 10000); - /** Change of the working directory on the FTP server - @param path new working directory to move on - @param timeout maximum time allow to execute the function - @return true if no error - */ - bool cd(const String& path, uint32_t timeout = 10000); - /** Download a file from the FTP server - @param localFileName name of the file on filesystem sent from FTP server - @param remoteFileName name of file on FTP server to retreive on filesystem - @param timeout maximum time allow to execute the function - @return true if no error + /** Create directory on the FTP server + @param name name of the directory to create + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool mkdir(const String& name, uint32_t timeout = 10000); + /** Delete directory on the FTP server + @param name name of the directory to delete + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool removeDirectory(const String&, uint32_t timeout = 10000); + /** Delete file on the FTP server + @param name name of the file to delete + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool removeFile(const String&, uint32_t timeout = 10000); + /** Rename file on the FTP server + @param oldName name of the file to rename + @param name new name of the file to rename + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool rename(const String& oldName, const String& name, uint32_t timeout = 10000); + /** Change of the working directory on the FTP server + @param path new working directory to move on + @param timeout maximum time allow to execute the function + @return true if no error + */ + bool cd(const String& path, uint32_t timeout = 10000); + /** Download a file from the FTP server + @param localFileName name of the file on filesystem sent from FTP server + @param remoteFileName name of file on FTP server to retreive on filesystem + @param timeout maximum time allow to execute the function, -1 infinite + @return true if no error Download a file in blocking mode until the timeout elapsed. - */ - bool download(const String& localFileName, const String& remoteFileName, uint32_t timeout = 10000); + */ + bool download(const String& localFileName, const String& remoteFileName, int32_t timeout = -1); /** Start file download from the FTP server @param localFileName name of the file on filesystem sent from FTP server - @param remoteFileName name of file on FTP server to retreive on filesystem + @param remoteFileName name of file on FTP server to retreive on filesystem @return true if no error Initialize the file download in non blocking mode. */ bool downloadStart(const String& localFileName, const String& remoteFileName); - /** Update download state + /** Update download state @param remoteFileName name of file on FTP server to retreive on filesystem @param localFileName name of the file on filesystem sent from FTP server @param showProgression if true show the downloading progression [%] @@ -118,10 +118,10 @@ class GSMFTP : public ModemUrcHandler { int downloadReady(const String& localFileName, bool showProgression); /** Upload a file to the FTP server @param localFileName name of the file on filesystem to send to FTP server - @param timeout maximum time allow to execute the function + @param timeout maximum time allow to execute the function, -1 infinite @return true if no error */ - bool upload(const String& localFileName, const String& remoteFileName, uint32_t timeout = 10000); + bool upload(const String& localFileName, const String& remoteFileName, int32_t timeout = -1); /** Start file upload to the FTP server @param localFileName name of the file on filesystem to send to FTP server @param remoteFileName name of the file on FTP server sent from filesystem @@ -134,83 +134,107 @@ class GSMFTP : public ModemUrcHandler { @return 1 : upload finished, 0 downloading, -1 an error occured Update the upload in non blocking mode. - When uploading no other request can be send to the server, so we can not display the progress + When uploading no other request can be send to the server, so we can not display the progress */ int uploadReady(); + /** Direct upload from local volatile memory to FTP server + @param data data to send + @param size data size to send + @param remoteFileName name of the file on FTP server + @param timeout maximum time before function return an error, -1 infinite + @return true if no error + */ + bool write(void* data, size_t size, const String& remoteFileName, int32_t timeout = -1); + /** Direct download from FTP server to local volatile memory + @param data data to received + @param size data size to received + @param remoteFileName name of the file on FTP server + @param timeout maximum time before function return an error, -1 infinite + @return true if no error + + If size is less than the remote file size, the data reception is aborted before the complete downloaded. + In this case the connection can be lost. + */ + bool read(void* data, size_t size, const String& remoteFileName, int32_t timeout = -1); + + + /** Print the error class and code of the last FTP operation @brief 0,0 mean no error otherwise {error class},{error code}. For the description refer to the documention : https://www.u-blox.com/sites/default/files/u-blox-CEL_ATCommands_%28UBX-13002752%29.pdf */ - void printError(); + void printError(); private: - static const uint32_t c_connectionTimeout = 10000; - - void handleUrc(const String&); - - bool _connected; - int _dirCreated; - int _dirChanged; - int _fileRemoved; - int _dirRemoved; - int _fileRenamed; - int _fileDownloaded; - int _fileUploaded; - GSMFTPElem* _fileInfo; - uint32_t _downloadDisplayTimeRef; - uint32_t _downloadRemoteFileSize; + static const uint32_t c_connectionTimeout = 10000; + + void handleUrc(const String&); + + int _connected; + int _dirCreated; + int _dirChanged; + int _fileRemoved; + int _dirRemoved; + int _fileRenamed; + int _fileDownloaded; + int _fileUploaded; + int _fileDirectUploaded; + int _fileDirectDownloaded; + GSMFTPElem* _fileInfo; + uint32_t _downloadDisplayTimeRef; + uint32_t _downloadRemoteFileSize; }; class GSMFTPElem { public: - struct Elem - { - String permissions; - uint32_t size; - uint32_t number; - String user; - String group; - String lastModified; - String name; - - Elem() :size(0), number(0) - {} - }; - - GSMFTPElem() :_elem(nullptr), _count(0){} - ~GSMFTPElem() {clear();} - - /** Append a new element in the file array - @param elem elem to append in the array - */ - void append(const Elem elem); - /** Show file information of the corresponding index - @param i index of the element to show - */ - void show(int i); - /** Clear the file array - */ - void clear(); - /** Get a file element - @param i file index to get the element - @return array element relative to the index, empty element if the index is out of range - */ - Elem elem(uint16_t i); - /** Get number of file found - @return number of element in the array - */ - uint32_t count() {return _count;} - /** Parse string containing file information - @param str string containing file information to parse - */ - void parse(const String& str); + struct Elem + { + String permissions; + uint32_t size; + uint32_t number; + String user; + String group; + String lastModified; + String name; + + Elem() :size(0), number(0) + {} + }; + + GSMFTPElem() :_elem(nullptr), _count(0) {} + ~GSMFTPElem() { clear(); } + + /** Append a new element in the file array + @param elem elem to append in the array + */ + void append(const Elem elem); + /** Show file information of the corresponding index + @param i index of the element to show + */ + void show(int i); + /** Clear the file array + */ + void clear(); + /** Get a file element + @param i file index to get the element + @return array element relative to the index, empty element if the index is out of range + */ + Elem elem(uint16_t i); + /** Get number of file found + @return number of element in the array + */ + uint32_t count() { return _count; } + /** Parse string containing file information + @param str string containing file information to parse + */ + void parse(const String& str); private: - Elem* _elem; - uint32_t _count; + Elem* _elem; + uint32_t _count; }; #endif diff --git a/src/Modem.cpp b/src/Modem.cpp index 4753584..e69abbb 100644 --- a/src/Modem.cpp +++ b/src/Modem.cpp @@ -172,6 +172,33 @@ size_t ModemClass::write(const uint8_t* buf, size_t size) return _uart->write(buf, size); } +size_t ModemClass::read(uint8_t* buf, size_t size, int32_t timeout) +{ + size_t res = 0; + uint32_t start = millis(); + + while ((((millis() - start) < timeout) || (timeout < 0)) + && (res < size)) { + if (_uart->available()) { + buf[res] = _uart->read(); + res++; + } + } + return res; +} + +void ModemClass::escapeSequence(uint32_t t1, uint32_t t2, bool waitResponse) +{ + delay(t1); + _uart->write('+'); + _uart->write('+'); + _uart->write('+'); + delay(t2); + if (waitResponse == true) { + _atCommandState = AT_RECEIVING_RESPONSE; + } +} + void ModemClass::send(const char* command) { if (_lowPowerMode) { @@ -305,7 +332,10 @@ void ModemClass::poll() int responseResultIndex = _buffer.lastIndexOf("OK\r\n"); if (responseResultIndex != -1) { _ready = 1; - } else { + } + else if (_buffer.lastIndexOf("CONNECT\r\n") != -1) { + _ready = 1; + }else { responseResultIndex = _buffer.lastIndexOf("ERROR\r\n"); if (responseResultIndex != -1) { _ready = 2; diff --git a/src/Modem.h b/src/Modem.h index 57e6152..f0ed31b 100644 --- a/src/Modem.h +++ b/src/Modem.h @@ -50,6 +50,8 @@ class ModemClass { size_t write(uint8_t c); size_t write(const uint8_t*, size_t); + size_t read(uint8_t*, size_t, int32_t timeout=-1); + void escapeSequence(uint32_t t1=1500, uint32_t t2=1500, bool waitResponse=false); void send(const char* command); void send(const String& command) { send(command.c_str()); } From d5bc9b94f73702ce085b140e9626a539993f32d4 Mon Sep 17 00:00:00 2001 From: tryhus Date: Thu, 17 Jan 2019 19:37:41 +0100 Subject: [PATCH 5/5] Add functions to stream FTP data --- examples/FTP/FTP.ino | 71 +++++++++++++++++++++++++-- src/GSMFTP.cpp | 113 ++++++++++++++++++++++++++++++++++++++++++- src/GSMFTP.h | 48 ++++++++++++++++++ 3 files changed, 227 insertions(+), 5 deletions(-) diff --git a/examples/FTP/FTP.ino b/examples/FTP/FTP.ino index f8e9d7e..22ca499 100644 --- a/examples/FTP/FTP.ino +++ b/examples/FTP/FTP.ino @@ -62,6 +62,7 @@ void setup() { void loop() { GSMFileSystemElem localFile; GSMFTPElem remoteFile; + String fileName; test("Connect to FTP server", ftp.connect(SECRET_FTP_HOST, SECRET_FTP_USER, SECRET_FTP_PASSWORD, SECRET_FTP_PORT)); @@ -139,7 +140,7 @@ void loop() { //upload volatile memory => FTP server //download FTP server => volatile memory - String fileName = "myFile.txt"; + fileName = "myFile.txt"; char bufferWR[128]; char bufferRD[128]; for (int i = 0; i < 128; ++i) { @@ -159,6 +160,18 @@ void loop() { test("Delete remote file", ftp.removeFile(fileName)); + //--- Test Stream data --- + + fileName = "FileToStream"; + test("Stream data to server", + StreamOut(fileName)); + + test("Stream data from server", + StreamIn(fileName)); + + test("Delete remote file", + ftp.removeFile(fileName)); + test("Disconnect to FTP server", ftp.disconnect()); @@ -179,7 +192,7 @@ bool downloadFileNonBlocking(const String localFileName, const String remoteFile //update download while (res == 0) { res = ftp.downloadReady(localFileName, true); - //do some job + //do something } return (res == 1); @@ -196,12 +209,64 @@ bool uploadFileNonBlocking(const String localFileName, const String remoteFileNa while (res == 0) { res = ftp.uploadReady(); - //do some job + //do something + } + + return (res == 1); +} + +bool StreamOut(const String& remoteFileName) +{ + int res = 0; + + //Start upload + if (ftp.streamOutStart(remoteFileName) == false) { + return false; + } + + //send data by packets + for (int i = 0; i < 1000; ++i) { + char buffer[128]; + snprintf(buffer, 128, "Line number %d\n", i); + ftp.streamOut(buffer, 128); + //do something + } + + while (res == 0) { + res = ftp.streamOutReady(); + //do something } return (res == 1); } +bool StreamIn(const String& remoteFileName) +{ + int res = 0; + bool dataConsistency = true; + + //Start download + if (ftp.streamInStart(remoteFileName) == false) { + return false; + } + + //receive data by packets + for (int i = 0; i < 1000; ++i) { + char buffer[128]; + ftp.streamIn(buffer, 128); + String test = String(buffer); + dataConsistency &= (test.indexOf(String("Line number " + String(i) + "\n")) >= 0); + //do something + } + + while (res == 0) { + res = ftp.streamInReady(); + //do something + } + + return ((res == 1) && (dataConsistency == true)); +} + bool test(const String& msg, bool function) { if (function == true) { diff --git a/src/GSMFTP.cpp b/src/GSMFTP.cpp index 98d8d28..b859f9a 100644 --- a/src/GSMFTP.cpp +++ b/src/GSMFTP.cpp @@ -31,7 +31,8 @@ GSMFTP::GSMFTP() : _fileDirectDownloaded(-1), _downloadDisplayTimeRef(0), _downloadRemoteFileSize(0), - _fileInfo(nullptr) + _fileInfo(nullptr), + _uploadRemainingBytes(0) { MODEM.addUrcHandler(this); } @@ -426,7 +427,7 @@ bool GSMFTP::write(void* data, size_t size, const String& remoteFileName, int32_ if (_connected != 1) { return false; } - _fileDirectUploaded = -2; + _fileDirectUploaded = -1; MODEM.send("AT+UFTPC=7,\"" + remoteFileName + "\""); String resp; @@ -492,6 +493,114 @@ bool GSMFTP::read(void* data, size_t size, const String& remoteFileName, int32_t return false; } +bool GSMFTP::streamOutStart(const String& remoteFileName) +{ + if (_connected != 1) { + return false; + } + _fileDirectUploaded = -2; + + MODEM.send("AT+UFTPC=7,\"" + remoteFileName + "\""); + + if (MODEM.waitForResponse(10000) != 1) { + return false; + } + return true; +} + +bool GSMFTP::streamOut(void* data, size_t size) +{ + if (_connected != 1) { + return false; + } + MODEM.write((const uint8_t*)data, size); + return true; +} + +int GSMFTP::streamOutReady() +{ + if (_connected != 1) { + return -1; + } + + if (_fileDirectUploaded == -2) { + MODEM.escapeSequence(1500, 0); + _fileDirectUploaded = -1; + } + + MODEM.poll(); + + if (_fileDirectUploaded == 0) { + return -1; + } + else if (_fileDirectUploaded == 1) { + return 1; + } + + return 0; +} + +bool GSMFTP::streamInStart(const String& remoteFileName) +{ + _fileDirectDownloaded = -2; + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + + GSMFTPElem remoteFile; + ls(remoteFile, remoteFileName); + _uploadRemainingBytes = remoteFile.elem(0).size; + if (_uploadRemainingBytes == 0) { + return false; + } + + MODEM.send("AT+UFTPC=6,\"" + remoteFileName + "\""); + + if (MODEM.waitForResponse(10000) != 1) { + return false; + } + + return true; +} + +int GSMFTP::streamIn(void* data, size_t size, int32_t timeout) +{ + if (_connected != 1) { + return -1; + } + + if (size > _uploadRemainingBytes) { + size -= _uploadRemainingBytes; + } + + uint32_t res = MODEM.read((uint8_t*)data, size, timeout); + _uploadRemainingBytes -= res; + + return ((_uploadRemainingBytes == 0) ? 1 : 0); +} + +int GSMFTP::streamInReady() +{ + if (_connected != 1) { + return -1; + } + + if ((_fileDirectDownloaded == -2) && (_uploadRemainingBytes > 0)) { + MODEM.escapeSequence(1000, 1000, true); + _fileDirectDownloaded = -1; + } + + MODEM.poll(); + + if ((_fileDirectDownloaded == 0) || (_fileDirectDownloaded == 1)) { + return 1; + } + + return 0; +} + + //--- GSMFTPElem GSMFTPElem::Elem GSMFTPElem::elem(uint16_t i) diff --git a/src/GSMFTP.h b/src/GSMFTP.h index ae29213..a43aa4f 100644 --- a/src/GSMFTP.h +++ b/src/GSMFTP.h @@ -156,9 +156,56 @@ class GSMFTP : public ModemUrcHandler { In this case the connection can be lost. */ bool read(void* data, size_t size, const String& remoteFileName, int32_t timeout = -1); + /** Start upload in stream mode + @param remoteFileName name of the file on FTP server + @return true if no error + Set the module in direct link mode. + After that it will establish a transparent end to end communication + with the data connection TCP socket via the serial interface. + No command to the module can be executed until the end of the transfer. + */ + bool streamOutStart(const String& remoteFileName); + /** Send data to FTP server + @param data data to send + @param size data size to send + @return true if no error + Send a packet of data to the FTP server. + */ + bool streamOut(void* data, size_t size); + /** Finished the data transfer to FTP server + @return 1 : transfer finished, 0 busy, -1 an error occured + Exit direct link mode then wait for the transmission to be completed. + */ + int streamOutReady(); + /** Start download in stream mode + @param remoteFileName name of the file on FTP server + @return true if no error + + Set the module in direct link mode. + After that it will establish a transparent end to end communication + with the data connection TCP socket via the serial interface. + No command to the module can be executed until the end of the transfer. + */ + bool streamInStart(const String& remoteFileName); + /** Send data to FTP server + @param data data to receive + @param size data size to receive + @param timeout maximum time before function return an error, -1 infinite + @return 1 : all data is received, 0 not all data is received, -1 an error occured + + Send a packet of data to the FTP server. + If all data is received the module will automatically exit from direct link mode. + */ + int streamIn(void* data, size_t size, int32_t timeout = -1); + /** Finished the data transfer to FTP server + @return 1 : transfer finished, 0 busy, -1 an error occured + + Exit direct link mode then wait for the transmission to be completed. + */ + int streamInReady(); /** Print the error class and code of the last FTP operation @brief 0,0 mean no error otherwise {error class},{error code}. @@ -185,6 +232,7 @@ class GSMFTP : public ModemUrcHandler { GSMFTPElem* _fileInfo; uint32_t _downloadDisplayTimeRef; uint32_t _downloadRemoteFileSize; + uint32_t _uploadRemainingBytes; }; class GSMFTPElem