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 new file mode 100644 index 0000000..22ca499 --- /dev/null +++ b/examples/FTP/FTP.ino @@ -0,0 +1,281 @@ +/* + 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; + +//this file must be present in the remote directory SECRET_FTP_REMOTE_DIR +const String c_downloadFileName = "downloadFile"; + +// initialize the library instance +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() { + 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)); + + 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 + + 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 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()); + + for (;;) + ; +} + +//Example of non blocking download functions +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 something + } + + return (res == 1); +} + +//Example of non blocking upload functions +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 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) { + Serial.print("OK - "); + } + else { + Serial.print("ERROR - "); + } + Serial.println(msg); + + return function; +} diff --git a/examples/FTP/arduino_secrets.h b/examples/FTP/arduino_secrets.h new file mode 100644 index 0000000..6408609 --- /dev/null +++ b/examples/FTP/arduino_secrets.h @@ -0,0 +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_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_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 new file mode 100644 index 0000000..b859f9a --- /dev/null +++ b/src/GSMFTP.cpp @@ -0,0 +1,774 @@ +/* + 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 + +GSMFTP::GSMFTP() : + _connected(-1), + _dirCreated(-1), + _dirChanged(-1), + _fileRemoved(-1), + _fileDownloaded(-1), + _fileUploaded(-1), + _fileDirectUploaded(-1), + _fileDirectDownloaded(-1), + _downloadDisplayTimeRef(0), + _downloadRemoteFileSize(0), + _fileInfo(nullptr), + _uploadRemainingBytes(0) +{ + MODEM.addUrcHandler(this); +} + +GSMFTP::~GSMFTP() +{ + MODEM.removeUrcHandler(this); +} + +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; + } + + _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(); + _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 == 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 == 1) { + 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(); + 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 != 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 != 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 != 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, int32_t timeout) +{ + 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 != 1) { + return false; + } + //get remote file informations + GSMFTPElem remoteFile; + ls(remoteFile, remoteFileName); + _downloadRemoteFileSize = remoteFile.elem(0).size; + _fileDownloaded = -1; + + 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 != 1) { + return -1; + } + + MODEM.poll(); + + if (_fileDownloaded == 0) { + if (showProgression == true) { + Serial.println("Failed to download the file."); + } + return -1; + } + 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; +} + +bool GSMFTP::upload(const String& localFileName, const String& remoteFileName, int32_t timeout) +{ + 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 != 1) { + return false; + } + + _fileUploaded = -1; + MODEM.send("AT+UFTPC=5,\"" + localFileName + "\",\"" + remoteFileName + "\""); + if (MODEM.waitForResponse(100) != 1) { + return false; + } + return true; +} + +int GSMFTP::uploadReady() +{ + if (_connected != 1) { + return -1; + } + + MODEM.poll(); + + if (_fileUploaded == 0) { + 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 != 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); +} + +bool GSMFTP::write(void* data, size_t size, const String& remoteFileName, int32_t timeout) +{ + uint32_t start = millis(); + if (_connected != 1) { + return false; + } + _fileDirectUploaded = -1; + + 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; +} + +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) +{ + 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++; +} + +void GSMFTPElem::clear() { + 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(); +} + +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); +} + +void GSMFTP::handleUrc(const String& urc) +{ + 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: 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); + } +} diff --git a/src/GSMFTP.h b/src/GSMFTP.h new file mode 100644 index 0000000..a43aa4f --- /dev/null +++ b/src/GSMFTP.h @@ -0,0 +1,288 @@ +/* + 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 GSMFTPElem; + +class GSMFTP : public ModemUrcHandler { + +public: + 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 + */ + 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 + @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, 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 + @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, -1 infinite + @return true if no error + */ + 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 + @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(); + /** 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); + /** 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}. + 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; + + 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; + uint32_t _uploadRemainingBytes; +}; + +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 new file mode 100644 index 0000000..5f5d251 --- /dev/null +++ b/src/GSMFileSystem.cpp @@ -0,0 +1,188 @@ +/* + 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 + +bool GSMFileSystem::ls(GSMFileSystemElem& file, bool show, uint32_t timeout) +{ + uint32_t start = millis(); + String response; + file.clear(); + 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; + } + file.setSize(i, size(file.elem(i).name)); + + if (show == true) { + file.show(i); + } + } + return true; +} + +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) +{ + MODEM.send("AT+UDELFILE=\"" + name + "\""); + if (MODEM.waitForResponse(10000) == 1) { + return true; + }else { + return false; + } +} + +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; + MODEM.send("AT+ULSTFILE=1"); + + if (MODEM.waitForResponse(1000, &response) == 1) { + if (response.startsWith("+ULSTFILE: ")) { + res = response.substring(11).toInt(); + } + } + return res; +} + +bool GSMFileSystem::write(const String& fileName, void* data, size_t size) +{ + MODEM.send("AT+UDWNFILE=\"" + fileName + "\"," + size); + + if (MODEM.waitForPrompt() == 1) { + MODEM.write((const uint8_t*)data, size); + if (MODEM.waitForResponse(10000) == 1) { + return true; + } + } + return false; +} + +bool GSMFileSystem::read(const String& fileName, void* data, size_t size) +{ + String response; + MODEM.send("AT+URDFILE=\"" + fileName + "\""); + + if (MODEM.waitForResponse(10000, &response) == 1) { + memcpy(data, response.c_str(), size); + return true; + } + return false; +} + +//--- 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 < _count) { + _elem[i].size = size; + } +} + +void GSMFileSystemElem::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++; +} + +void GSMFileSystemElem::clear() { + if (_elem != nullptr) { + delete[] _elem; + _elem = nullptr; + } + _count = 0; +} + +void GSMFileSystemElem::show(int i) +{ + if (i >= _count) { + return; + } + Serial.print(_elem[i].name); + Serial.print(" "); + Serial.println(_elem[i].size); +} + +void GSMFileSystemElem::parse(const String& str) +{ + String res = str; + int i = res.indexOf('"')+1; + int j = res.indexOf('"', i); + + while ((i > 0) && (j > 0)){ + Elem elem; + elem.name = res.substring(i, j); + res = res.substring(j + 1); + append(elem); + i = res.indexOf('"') + 1; + j = res.indexOf('"', i); + } +} + +GSMFileSystem FILESYSTEM; diff --git a/src/GSMFileSystem.h b/src/GSMFileSystem.h new file mode 100644 index 0000000..ebc4741 --- /dev/null +++ b/src/GSMFileSystem.h @@ -0,0 +1,125 @@ +/* + 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 GSMFileSystemElem; + +class GSMFileSystem { + +public: + + /** 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(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); + /** Delete files of the file system + @param files set of files to delete + @return true if no error + */ + 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 + @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); + +}; + +class GSMFileSystemElem { + +public: + struct Elem + { + String name; + uint32_t size; + + Elem() :size(0) + {} + }; + + 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 diff --git a/src/Modem.cpp b/src/Modem.cpp index 2e73cd1..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) { @@ -279,13 +306,36 @@ 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"); 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()); }