diff --git a/.github/workflows/platformio-ci.yml b/.github/workflows/platformio-ci.yml index 2b1b710d..31536c8e 100644 --- a/.github/workflows/platformio-ci.yml +++ b/.github/workflows/platformio-ci.yml @@ -83,9 +83,9 @@ jobs: - name: Install PlatformIO Core run: pip install --upgrade platformio - - name: Install SDL2 - run: sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" && sudo apt-get update -y -qq && sudo apt-get install libsdl2-dev - + - name: Install SDL2 and libcurl + run: sudo add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" && sudo apt-get update -y -qq && sudo apt-get install libsdl2-dev && sudo apt-get install libcurl4-openssl-dev + - name: Run tests for Linux run: pio test -e linux --json-output-path linux-test-report.json --junit-output-path linux-test-report.xml diff --git a/README.md b/README.md index fa0f8818..12f8fd2f 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,162 @@ -# PaxOS 9 +# Build Instructions -![logo](https://github.com/paxo-phone/PaxOS-9/assets/45568523/ddb3b517-605c-41b4-8c1e-c8e5d156431b) +If you are using CLion, please use the [CLion Instructions](#clion-instructions). [![PlatformIO CI](https://github.com/paxo-phone/PaxOS-9/actions/workflows/platformio-ci.yml/badge.svg)](https://github.com/paxo-phone/PaxOS-9/actions/workflows/platformio-ci.yml) -**PaxOS 9** est la dernière version du **PaxOS**, un système d'exploitation léger destiné aux PaxoPhones. +### Install PlatformIO Core -- [Licence](#licence) -- [Contact](#contact) -- [En savoir plus](#see-more) -- [Contributeurs](#contributors) +> https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html#local-download-macos-linux-windows -# Licence -Ce projet est distribué sous licence AGPL-3.0. +> https://docs.platformio.org/en/latest/core/installation/shell-commands.html#piocore-install-shell-commands -# Contact +### Install SDL2 -Vous pouvez nous contacter via notre [site internet](https://www.paxo.fr) ou notre [serveur Discord](https://discord.com/invite/MpqbWr3pUG). +_(If you are on Windows, skip this step)_ -# En savoir plus -En savoir plus sur [paxo.fr](https://www.paxo.fr) +```shell +# for linux +sudo apt-get install libsdl2-dev +# for macos using homebrew +brew install sdl2 +``` -# Contributeurs +### Install libcurl - - - +_(If you are on Windows, skip this step)_ + + +```shell +# for linux +sudo apt-get install libcurl4-openssl-dev +# for macos using homebrew +brew install curl +``` + +## Clone the repository + +```shell +git clone https://github.com/paxo-phone/PaxOS-9.git +``` + +## Initialize the project + +```shell +pio project init +``` + +## Build + +```shell +# Build for ESP32 +pio run -e esp32dev + +# Build for Windows +pio run -e windows + +# Build for Linux +pio run -e linux + +# Build for MacOS +pio run -e macos +``` + +## Run on ESP32 + +```shell +pio run -t upload -e esp32dev +``` + +## Run Tests + +```shell +pio test -e test +``` + +## Troubleshooting + +### macOS +- If you get a popup saying that the program is from an unidentified developer, do `xattr -d com.apple.quarantine program` in the same directory as the `program`. +- If no program is launching at the end of the build when calling `pio run -e macos`, try to run the executable using `.pio/build/macos/program`. +- If you get an error similar to + ``` + dyld[xxxxx]: Library not loaded: @rpath/libSDL2-2.0.0.dylib + Referenced from: /Users/username/PaxOS-9/.pio/build/macos/program + Reason: no LC_RPATH's found + zsh: abort .pio/build/macos/program + ``` + Try to do ```DYLD_LIBRARY_PATH="`brew --prefix sdl2`/lib" .pio/build/macos/program``` and if it still doesn't work and you haven't installed SDL2 locally as decribed in the next bullet point (otherwise run ```DYLD_LIBRARY_PATH="`eval echo ~$USER`/sdl2/lib" .pio/build/macos/program```) do: + 1. Get the location of SDL's dynamic library by executing `brew info sdl2`, you should get a path similar to `/opt/homebrew/Cellar/sdl2/2.28.5` + 2. Run `ls {the path you got from the last command}/lib/libSDL2-2.0.0.dylib`. If you get the exact same path as an output you're ready to go. Otherwise install SDL as described above in this document and retry the procedure. + 3. Re-run the command by adding `DYLD_LIBRARY_PATH="{the path you got from step 1}/lib/"` as a prefix before the command like this `DYLD_LIBRARY_PATH="{the path you got from step 1}/lib/ .pio/build/macos/program` +- When compiling the project using pio you might encounter an error where your shell can't find `pio` even if you installed it via the quick-setup. To fix that, execute `pio` directly from the binaries path that is located at `/Users/username/.platformio/penv/bin/pio`. If you have PaxOS9 installed directly in your home (i.e. `/Users/username/PaxOS9`), run pio via `../.platformio/penv/bin/pio`. To build for macOS it would give something similar this `../.platformio/penv/bin/pio run -e macos`. +- If you get the `unknown platform` error when compiling, that probalby means that SDL2 is not installed where it should or even just not installed at all. If you have admin permissions for your machine run `brew install sdl2`. If you don't, then install SDL2 locally by following those steps: + 1. Execute the following command (don't worry it won't hack into your computer) + ``` + cd `eval echo ~$USER` && rm -rf sdl2-build && rm -rf sdl2 && mkdir sdl2 && git clone https://github.com/libsdl-org/SDL.git -b SDL2 sdl2-build && cd sdl2-build && mkdir build && cd build && ../configure --prefix=`eval echo ~$USER`/sdl2 && make -j`sysctl -n hw.ncpu` && make install && cd `eval echo ~$USER` && rm -rf sdl2-build && echo "Installed SDL at `eval echo ~$USER`/sdl2" && echo "\033[0;32mThe two lines to add to the macos target (\033[1;34mbuild_flags\033[0;32m) of the \033[1;34mplatform.ini\033[0;32m in your PaxOS9 directory are \033[1;33m-I`eval echo ~$USER`/sdl2/include\033[0;32m and \033[1;33m-L`eval echo ~$USER`/sdl2/lib\033[0;32m" + ``` + 2. Follow the instructions that the command gave you (add the two lines). The place where you should put the lines looks like this (the filename is called `platform.ini` and is present in the `PaxOS9` directory): + ``` + [env:macos] + platform = native + lib_deps = + x + x + x + test_framework = googletest + build_flags = + x + x + x + x + -I/opt/homebrew/include/SDL2 + -L/opt/homebrew/lib + -->PLACE THE LINES SEPARATED BY A NEW LINE HERE <-- + ``` + +# CLion Instructions + +## Install dependencies + +### Install PlatformIO plugin + +* Launch CLion +* Install the ``PlatformIO for CLion`` Plugin by ``JetBrains`` +* Restart the IDE + +### Install MinGW-w64 + +Even if CLion comes with a full MinGW installation.\ +This is required by PlatformIO, because it uses ``g++`` for compilation.\ +You _may_ want to try to add the CLion's MinGW installation to Path, but it's not recommended. + +* Go to https://winlibs.com/ +* Select the ``Zip archive`` for ``GCC 13.2.0 (with POSIX threads) + LLVM/Clang/LLD/LLDB 17.0.6 + MinGW-w64 11.0.1 (UCRT) - release 4`` for ``Win64``, or [direct download](https://github.com/brechtsanders/winlibs_mingw/releases/download/13.2.0posix-17.0.6-11.0.1-ucrt-r4/winlibs-x86_64-posix-seh-gcc-13.2.0-llvm-17.0.6-mingw-w64ucrt-11.0.1-r4.zip) +* Extract it in ``C:\mingw64`` (So you have the ``bin`` folder at ``C:\mingw64\bin``) +* Add ``C:\mingw64\bin`` to your PATH + +## Clone the repository + +* On the ``Welcome to CLion`` window, select ``Get from VCS`` (or go to ``File -> New -> Project from Version Control...``) +* Enter the repository URL (``https://github.com/paxo-phone/PaxOS-9.git``) +* Open the project as a ``PlatformIO`` project, not ``CMake`` (this step is very important) +* Wait the IDE to import the project (this can take several minutes) + +## Build + +Don't forget to re-build the project before running it,\ +you can get weird behaviors by not doing so. + +* Select the correct target +* Click the ``Build`` button + +## Run on ESP32 + +* Select the ``esp32dev`` target +* Click on the ``Run`` button _(don't forget to build before)_ + +## Run Tests + +* Select the ``test`` target +* Click on the ``Run`` button _(don't forget to build before)_ diff --git a/lib/applications/src/app.hpp b/lib/applications/src/app.hpp index b04fab14..e0452514 100644 --- a/lib/applications/src/app.hpp +++ b/lib/applications/src/app.hpp @@ -144,4 +144,4 @@ namespace AppManager #include -#endif \ No newline at end of file +#endif diff --git a/lib/backtrace/src/backtrace.cpp b/lib/backtrace/src/backtrace.cpp index 42c34e27..c26103b5 100644 --- a/lib/backtrace/src/backtrace.cpp +++ b/lib/backtrace/src/backtrace.cpp @@ -52,7 +52,7 @@ namespace backtrace_saver { } - re_restart_debug_t getCurrentBacktrace() + /*re_restart_debug_t getCurrentBacktrace() { re_restart_debug_t oldData = _debug_info; debugUpdate(); @@ -62,7 +62,7 @@ namespace backtrace_saver { _debug_info = oldData; return returnData; - } + }*/ bool saveBacktrace() { diff --git a/lib/backtrace/src/backtrace.hpp b/lib/backtrace/src/backtrace.hpp index 0c829a0c..d9d6c6ff 100644 --- a/lib/backtrace/src/backtrace.hpp +++ b/lib/backtrace/src/backtrace.hpp @@ -19,8 +19,6 @@ namespace backtrace_saver { std::string getBacktraceMessage(); - backtrace_saver::re_restart_debug_t getCurrentBacktrace(); - bool saveBacktrace(); void backtraceMessageGUI(); diff --git a/lib/gsm/src/gsm.cpp b/lib/gsm/src/gsm.cpp index 9e803659..8dec79e2 100644 --- a/lib/gsm/src/gsm.cpp +++ b/lib/gsm/src/gsm.cpp @@ -1144,6 +1144,9 @@ namespace GSM if(o.find("+CSQ:") != std::string::npos) { networkQuality = atoi(o.substr(o.find("+CSQ: ") + 5, o.find(",") - o.find("+CSQ: ") - 5).c_str()); + } else + { + networkQuality = 0; } //std::cout << "networkQuality: " << networkQuality << std::endl; } @@ -1201,7 +1204,7 @@ namespace GSM keys.push_back({"+CMTI:", &GSM::onMessage}); keys.push_back({"VOICE CALL: END", &GSM::onHangOff}); keys.push_back({"VOICE CALL: BEGIN", [](){ state.callState = CallState::CALLING; }}); - keys.push_back({"+HTTPACTION: ", &GSM::HttpRequest::received}); + keys.push_back({"+HTTPACTION: ", &GSM::handleIncomingResponse}); coresync.lock(); @@ -1221,7 +1224,7 @@ namespace GSM data = ""; checkRequest(); - HttpRequest::manage(); + requestsLoopCycle(); } } }; diff --git a/lib/gsm/src/gsm.hpp b/lib/gsm/src/gsm.hpp index 37f96c77..d5cef224 100644 --- a/lib/gsm/src/gsm.hpp +++ b/lib/gsm/src/gsm.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #define BAUDRATE 921600 @@ -127,57 +129,22 @@ namespace GSM // set flight mode void setFlightMode(bool mode); + // Network - struct HttpHeader - { - enum Method - { - GET, - POST - }; - - std::string url; - Method httpMethod; - std::string body; - }; - class HttpRequest // todo add a timeout if the callback is never called + add inner buffer for 2 cores requests - { - public: - HttpRequest(HttpHeader header); - ~HttpRequest(); + extern std::shared_ptr currentRequest; - void send(std::function callback); // return callback + extern std::vector> hTTPRequests; - HttpHeader header; - size_t readChunk(char* buffer); - void close(); - - enum RequestState - { - SETUP, // the data is set up - WAITING, // waiting for the system to send the request - SENT, // the request has been sent, wait for the result - RECEIVED, // the request has been received, waiting for the callback to read - END, - ENDED // the request has ended, need to be deleted - }; - - RequestState state = RequestState::SETUP; - std::function callback; - - static std::vector requests; - static HttpRequest* currentRequest; - static void manage(); - static void received(); - - private: - uint64_t dataSize = 0; - uint64_t timeout = 0; // date at which the request will timeout - uint64_t readed = 0; - - void fastKill(uint8_t code = 400); - }; + void requestsLoopCycle(); + + void sendRequest(std::shared_ptr request); + + void handleIncomingResponse(); + + size_t readResponseDataChunk(char* buffer); + + void closeRequest(uint16_t code); std::string getCurrentTimestamp(); // return the current timestamp formated std::string getCurrentTimestampNoSpaces(); // return the current timestamp formated without spaces diff --git a/lib/gsm/src/gsm_network.cpp b/lib/gsm/src/gsm_network.cpp index 41d0566d..8017aa8a 100644 --- a/lib/gsm/src/gsm_network.cpp +++ b/lib/gsm/src/gsm_network.cpp @@ -1,4 +1,5 @@ #include "gsm.hpp" +#include #include #include @@ -8,48 +9,46 @@ namespace GSM { - std::vector HttpRequest::requests; - HttpRequest* HttpRequest::currentRequest = nullptr; + std::shared_ptr currentRequest = nullptr; + std::vector> hTTPRequests; - HttpRequest::HttpRequest(HttpHeader header) : header(header) + void sendRequest(std::shared_ptr request) { - } + hTTPRequests.push_back(request); + request->state = network::URLSessionDataTask::State::Waiting; - HttpRequest::~HttpRequest() - { - close(); + request->updateTimeout(); } - void HttpRequest::send(std::function callback) + size_t readResponseDataChunk(char* buffer) // read a single chunk of 1024 bytes { - if(callback == nullptr) - { - state = RequestState::ENDED; - } - - requests.push_back(this); + std::cout << "---1" << std::endl; + if (currentRequest.get() == nullptr && currentRequest->response != std::nullopt) + return 0; - state = RequestState::WAITING; - timeout = millis() + 10000; - this->callback = callback; - } + std::cout << "---2" << std::endl; - size_t HttpRequest::readChunk(char* buffer) // read a single chunk of 1024 bytes - { - if(state != RequestState::RECEIVED) + if(currentRequest->state != network::URLSessionDataTask::State::ResponseReceived) return 0; + std::cout << "---3" << std::endl; + + network::URLResponse requestResponse = currentRequest->response.value(); + #ifdef ESP_PLATFORM - timeout = millis() + 10000; + currentRequest->updateTimeout(); GSM::coresync.lock(); uint64_t timer = millis(); // pour le timeout - uint64_t timeout_block = 3000; // timeout de 1 secondes + uint64_t timeout_block = 3000; // timeout de 3 secondes + + uint64_t nextBlockSize = std::min(uint64_t(1024), requestResponse.responseBodySize - requestResponse.readPosition); - uint64_t nextBlockSize = std::min(uint64_t(1024), dataSize - readed); + std::cout << "---4" << std::endl; - //std::cout << "Reading chunk: " << nextBlockSize << std::endl; + download(); + std::cout << "Reading chunk: " << nextBlockSize << std::endl; gsm.println("AT+HTTPREAD=0,1024\r"); @@ -65,7 +64,9 @@ namespace GSM { std::cout << "Timeout" << std::endl; nextBlockSize = 0; - close(); + GSM::coresync.unlock(); + closeRequest(705); + return 0; } else { @@ -74,15 +75,20 @@ namespace GSM GSM::data += gsm.readString().c_str(); - //std::cout << "Chunk readed" << std::endl; + std::cout << "Chunk readed" << std::endl; - readed += nextBlockSize; + requestResponse.readPosition += nextBlockSize; - if (readed == dataSize) + if (requestResponse.readPosition == requestResponse.responseBodySize) { - close(); + GSM::coresync.unlock(); + currentRequest->response = std::make_optional(requestResponse); + closeRequest(requestResponse.statusCode); // response code is not important here as the response has already been handled + return nextBlockSize; } + currentRequest->response = std::make_optional(requestResponse); + GSM::coresync.unlock(); return nextBlockSize; @@ -91,108 +97,112 @@ namespace GSM return 0; } - void HttpRequest::close() - { - if(state == RequestState::ENDED) - return; - - GSM::send("AT+HTTPTERM", "AT+HTTPTERM", 1000); - - state = RequestState::ENDED; - requests.erase(std::remove(requests.begin(), requests.end(), this), requests.end()); - } - - void HttpRequest::fastKill(uint8_t code) - { - close(); - callback(code, 0); + void closeRequest(uint16_t code) + { + if (currentRequest.get() != nullptr) + { + if (currentRequest->state != network::URLSessionDataTask::State::Finished && currentRequest->state != network::URLSessionDataTask::State::Waiting) + { + // request already sent, we need to close it + GSM::send("AT+HTTPTERM", "AT+HTTPTERM", 1000); + currentRequest->state = network::URLSessionDataTask::State::Finished; + } else if (currentRequest->state == network::URLSessionDataTask::State::Waiting) { + // request has not been sent yet + currentRequest->state = network::URLSessionDataTask::State::Finished; + } + hTTPRequests.erase(std::remove(hTTPRequests.begin(), hTTPRequests.end(), currentRequest), hTTPRequests.end()); + currentRequest->handleResponse(code, 0); // handle the response if it has not been done yet + currentRequest = nullptr; + } } - void HttpRequest::manage() + void requestsLoopCycle() { - if(currentRequest == nullptr) + if(GSM::currentRequest.get() == nullptr) { - for (uint8_t i = 0; i < requests.size(); i++) + for (uint8_t i = 0; i < hTTPRequests.size(); i++) { - if(requests[i]->state == HttpRequest::RequestState::WAITING) + if(hTTPRequests[i]->state == network::URLSessionDataTask::State::Waiting) { - currentRequest = requests[i]; + currentRequest = hTTPRequests[i]; break; } } } - if(currentRequest != nullptr) + if(GSM::currentRequest.get() != nullptr) { switch (currentRequest->state) { - case HttpRequest::RequestState::WAITING: - if(GSM::send("AT+HTTPINIT", "AT+HTTPINIT", 500).find("OK") == std::string::npos) // setup http + case network::URLSessionDataTask::State::Waiting: // request should be sent { - currentRequest->fastKill(); - break; - } - else - { - if(currentRequest->header.httpMethod == HttpHeader::Method::GET) + std::string o = GSM::send("AT+HTTPINIT", "AT+HTTPINIT", 1000); + currentRequest->state = network::URLSessionDataTask::State::Running; + if(currentRequest->isOverTimeout()) // setup http + { + closeRequest(708); // timout exceeded + break; + } + else { - GSM::send("AT+HTTPPARA=\"URL\",\"" + currentRequest->header.url + "\"\r", "AT+HTTPPARA", 500); - if(GSM::send("AT+HTTPACTION=0", "AT+HTTPACTION", 5000).find("OK") == std::string::npos) + if(currentRequest->request.method == network::URLRequest::HTTPMethod::GET) { - currentRequest->fastKill(); - break; + GSM::send("AT+HTTPPARA=\"URL\",\"" + currentRequest->request.url.absoluteString + "\"\r", "AT+HTTPPARA", 500); + if(GSM::send("AT+HTTPACTION=0", "AT+HTTPACTION", 5000).find("OK") == std::string::npos) + { + closeRequest(706); + break; + } } - } - else if(currentRequest->header.httpMethod == HttpHeader::Method::POST) - { - // Set URL for POST request - GSM::send("AT+HTTPPARA=\"URL\",\"" + currentRequest->header.url + "\"\r", "AT+HTTPPARA", 500); - - // Set content type (assuming JSON, adjust if needed) - GSM::send("AT+HTTPPARA=\"CONTENT\",\"application/json\"\r", "AT+HTTPPARA", 500); - - // Prepare to send data - int dataLength = currentRequest->header.body.length(); - GSM::send("AT+HTTPDATA=" + std::to_string(dataLength) + ",10000", "DOWNLOAD", 1000); - - // Send the actual POST data - GSM::send(currentRequest->header.body, "OK", 500 + dataLength * 8 * BAUDRATE); // wait for the full data transfer - - // Perform POST action - if(GSM::send("AT+HTTPACTION=1", "AT+HTTPACTION", 5000).find("OK") == std::string::npos) + else if(currentRequest->request.method == network::URLRequest::HTTPMethod::POST) { - currentRequest->fastKill(); + // Set URL for POST request + GSM::send("AT+HTTPPARA=\"URL\",\"" + currentRequest->request.url.absoluteString + "\"\r", "AT+HTTPPARA", 500); + + // Set content type (assuming JSON, adjust if needed) + GSM::send("AT+HTTPPARA=\"CONTENT\",\"application/json\"\r", "AT+HTTPPARA", 500); + + // Prepare to send data + int dataLength = currentRequest->request.httpBody.length(); + GSM::send("AT+HTTPDATA=" + std::to_string(dataLength) + ",10000", "DOWNLOAD", 1000); + + // Send the actual POST data + GSM::send(currentRequest->request.httpBody, "OK", 500 + dataLength * 8 * BAUDRATE); // wait for the full data transfer + + // Perform POST action + if((GSM::send("AT+HTTPACTION=1", "AT+HTTPACTION", 5000)).find("OK") == std::string::npos) + { + closeRequest(707); + break; + } + } else { + closeRequest(704); // unsupported method break; } - } - currentRequest->state = HttpRequest::RequestState::SENT; - } - - break; - case HttpRequest::RequestState::SENT: - // let the key be received - if(millis() > currentRequest->timeout) - { - currentRequest->fastKill(504); + currentRequest->updateTimeout(); + } } break; - case HttpRequest::RequestState::RECEIVED: + case network::URLSessionDataTask::State::Paused: // TODO? but as the simcom can handle only 1 request at a time it might not be useful + case network::URLSessionDataTask::State::Running: + case network::URLSessionDataTask::State::ResponseReceived: // let the app read the data - if(millis() > currentRequest->timeout) + if(currentRequest->isOverTimeout()) { - currentRequest->close(); - break; + closeRequest(708); } break; - case HttpRequest::RequestState::END: - currentRequest = nullptr; + case network::URLSessionDataTask::State::Cancelled: + case network::URLSessionDataTask::State::Finished: + closeRequest(709); // should happen rarely as the request should have already been removed from the list break; + } } } - void HttpRequest::received() + void handleIncomingResponse() { std::regex pattern(".*\\+HTTPACTION: (\\d+),(\\d+),(\\d+)\\r\\n.*"); std::smatch match; @@ -203,14 +213,15 @@ namespace GSM int status = std::stoi(match[2].str()); int size = std::stoi(match[3].str()); + std::cout << data << std::endl; + std::cout << "action: " << action << " status: " << status << " size: " << size << std::endl; - if(currentRequest != nullptr) + if(currentRequest.get() != nullptr) { - currentRequest->state = RequestState::RECEIVED; - currentRequest->dataSize = size; - currentRequest->timeout = millis() + 10000; - currentRequest->callback(status, size); + currentRequest->updateTimeout(); + //currentRequest->handleResponse(status, size); + eventHandlerApp.setTimeout(new Callback<>(std::bind(&network::URLSessionDataTask::handleResponse, currentRequest, status, size)), 0); } } } diff --git a/lib/network/README.md b/lib/network/README.md index e69de29b..153fc617 100644 --- a/lib/network/README.md +++ b/lib/network/README.md @@ -0,0 +1,73 @@ +Based on Swift's Networking Foundation + +class URLSession { + URLSession defaultInstance; + + URLSession(); + // URLSession(URLSessionConfiguration sessionWithConfiguration); + + // URLSessionConfiguration configuration; -> donnerait les configs de base des tasks sans URLRequest mais juste URL + + (URLSessionDataTask *) dataTaskWithURL(URL url, (void (^)(Data *data, URLResponse *response, Error *error)), completionHandler); + (URLSessionDataTask *) dataTaskWithRequest(URLRequest request, (void (^)(Data *data, URLResponse *response, Error *error)), completionHandler); + + + (URLSessionDownloadTask *) downloadTaskWithURL(URL url, (void (^)(URL location, URLResponse *response, Error *error)) completionHandler); + (URLSessionDownloadTask *) downloadTaskWithRequest(URLRequest request, (void (^)(URL *location, URLResponse *response, Error *error)) completionHandler); + + + (URLSessionUploadTask *) uploadTaskWithRequest(URLRequest request, Data *bodyData, (void (^)(Data *data, URLResponse *response, Error *error)) completionHandler); + (URLSessionUploadTask *) uploadTaskFromFileWithRequest(URLRequest request, URL fileURL, (void (^)(Data *data, URLResponse *response, Error *error)) completionHandler); + + + (URLSessionWebSocketTask *) webSocketTaskWithURL(URL url); // avec des closure delegate en plus + (URLSessionWebSocketTask *) webSocketTaskWithRequest(URLRequest request); // avec des closure delegate en plus + + vector getAllTasks(); +} + +struct URLRequest { + URLRequest(URL url, HTTPMethod httpMethod = GET, Data* httpBody = nullptr) // et le reste peut être set avec des options + + HTTPMethod httpMethod; + + URL url; + + Data* httpBody; + + map allHTTPHeaderFields; + + uint64_t timeoutInterval; + + enum HTTPMethod { + GET, POST, PUT, DELETE + } +} + +virtual class URLSessionTask { + void cancel(); + + void resume(); + + void suspend(); + + State state; + + double progress; // y'aura aussi des callbacks pour observer ça + + uint64_t countOfBytesReceived; + + uint64_t countOfBytesExpectedToSend; + + enum State { + running, suspended, cancelling, completed + } +} + +Pour ce qui est des URLSessionDataTask, URLSessionDownloadTask, URLSessionUploadTask et URLSessionWebSocketTask qui sont toutes dérivées de URLSessionTask, elles ont des callbacks en plus de base en fonction de ce qu'elles font. + + + + + + diff --git a/lib/network/network.hpp b/lib/network/network.hpp index e69de29b..4ab482d8 100644 --- a/lib/network/network.hpp +++ b/lib/network/network.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "src/NetworkManager.hpp" +#include "src/URL.hpp" +#include "src/URLRequest.hpp" +#include "src/URLResponse.hpp" +#include "src/URLSession.hpp" +#include "src/URLSessionDataTask.hpp" +#include "src/URLSessionTask.hpp" \ No newline at end of file diff --git a/lib/network/src/NetworkManager.cpp b/lib/network/src/NetworkManager.cpp new file mode 100644 index 00000000..c2210a82 --- /dev/null +++ b/lib/network/src/NetworkManager.cpp @@ -0,0 +1,103 @@ +#include "NetworkManager.hpp" + +#ifdef ESP_PLATFORM + #include +#endif +#include +#include +#include + +#include + +namespace network +{ + std::shared_ptr NetworkManager::sharedInstance/* = std::make_shared()*/; + + void init() + { + NetworkManager::sharedInstance = std::make_shared(); + URLSession::defaultInstance = std::make_shared(); + } + + NetworkManager::NetworkManager() + { + #ifdef ESP_PLATFORM + this->turnOFFWiFi(); + #endif + curl_global_init(CURL_GLOBAL_ALL); + } + + const void NetworkManager::connectToWiFi(const std::string& ssid) + { + #ifdef ESP_PLATFORM + WiFi.begin(ssid.c_str()); + #endif + } + + const void NetworkManager::connectToWiFi(const std::string& ssid, const std::string& password) + { + #ifdef ESP_PLATFORM + WiFi.begin(ssid.c_str(), password.c_str()); + #endif + } + + const void NetworkManager::disconnectWiFi(void) + { + #ifdef ESP_PLATFORM + WiFi.disconnect(); + #endif + } + + const void NetworkManager::turnOFFWiFi(void) + { + #ifdef ESP_PLATFORM + WiFi.mode(WIFI_OFF); + #endif + } + + const void NetworkManager::turnONWiFi(void) + { + #ifdef ESP_PLATFORM + WiFi.mode(WIFI_STA); + #endif + } + + const std::string NetworkManager::currentWiFiSSID() + { + #ifdef ESP_PLATFORM + return WiFi.SSID().c_str(); + #else + return ""; + #endif + } + + const bool NetworkManager::isConnected() + { + return this->isWiFiConnected() || this->isGSMConnected(); + } + + const bool NetworkManager::isWiFiConnected() + { + #ifdef ESP_PLATFORM + return WiFi.status() == WL_CONNECTED; + #else + int pingExitCode = system("ping -c1 -s1 8.8.8.8 > /dev/null 2>&1"); // ping Google's DNS server and return the exit code + if (pingExitCode == 0) + { + return true; + } else + { + return false; + } + #endif + } + + const bool NetworkManager::isGSMConnected() + { + #ifdef ESP_PLATFORM + return GSM::getNetworkStatus() > 0; + #else + return false; + #endif + } +} \ No newline at end of file diff --git a/lib/network/src/NetworkManager.hpp b/lib/network/src/NetworkManager.hpp new file mode 100644 index 00000000..aa45dc05 --- /dev/null +++ b/lib/network/src/NetworkManager.hpp @@ -0,0 +1,41 @@ +#ifndef NETWORKMANAGER_HPP +#define NETWORKMANAGER_HPP + +#include +#include +#include + +namespace network +{ + void init (); + + class NetworkManager + { + public: + NetworkManager(); + + static std::shared_ptr sharedInstance; + + const void connectToWiFi(const std::string& ssid); + + const void connectToWiFi(const std::string& ssid, const std::string& passwd); + + const void disconnectWiFi(); + + const void turnOFFWiFi(); + + const void turnONWiFi(); + + const std::string currentWiFiSSID(); + + const bool isConnected(); + + const bool isWiFiConnected(); + + const bool isGSMConnected(); + + private: + }; +} + +#endif // NETWORKMANAGER_HPP \ No newline at end of file diff --git a/lib/network/src/ProgressHandler.hpp b/lib/network/src/ProgressHandler.hpp new file mode 100644 index 00000000..24c9fc8c --- /dev/null +++ b/lib/network/src/ProgressHandler.hpp @@ -0,0 +1,11 @@ +#ifndef PROGRESSHANDLER_HPP +#define PROGRESSHANDLER_HPP + +#include + +namespace network +{ + typedef std::function ProgressHandler; +} + +#endif // PROGRESSHANDLER_HPP \ No newline at end of file diff --git a/lib/network/src/URL.cpp b/lib/network/src/URL.cpp new file mode 100644 index 00000000..b609bc61 --- /dev/null +++ b/lib/network/src/URL.cpp @@ -0,0 +1,14 @@ +#include "URL.hpp" + +#include + + +namespace network +{ + URL::URL(const std::string& string) : absoluteString(string) {} + + bool URL::isValid() const + { + return std::regex_match(absoluteString, std::regex("([a-zA-Z]+)://([^/ :]+)(:([0-9]+))?(/([^ #?]+)?([^ #]+)?)?")); + } +} \ No newline at end of file diff --git a/lib/network/src/URL.hpp b/lib/network/src/URL.hpp new file mode 100644 index 00000000..69cdcf5d --- /dev/null +++ b/lib/network/src/URL.hpp @@ -0,0 +1,18 @@ +#ifndef URL_HPP +#define URL_HPP + +#include + +namespace network +{ + struct URL + { + URL(const std::string& string); + + const std::string absoluteString; + + bool isValid() const; + }; +} + +#endif // URL_HPP \ No newline at end of file diff --git a/lib/network/src/URLRequest.hpp b/lib/network/src/URLRequest.hpp new file mode 100644 index 00000000..997a57e0 --- /dev/null +++ b/lib/network/src/URLRequest.hpp @@ -0,0 +1,32 @@ +#ifndef URLREQUEST_HPP +#define URLREQUEST_HPP + +#include +#include +#include +#include "URL.hpp" + +namespace network { + struct URLRequest { + enum HTTPMethod { + GET, + POST, + PUT, + DELETE + }; + + URLRequest(URL url) : url(url), method(GET), timeoutInterval(10000) {} + + HTTPMethod method; + + URL url; + + std::string httpBody; + + std::map httpHeaderFields; + + uint64_t timeoutInterval; // in ms + }; +} + +#endif // URLREQUEST_HPP \ No newline at end of file diff --git a/lib/network/src/URLResponse.hpp b/lib/network/src/URLResponse.hpp new file mode 100644 index 00000000..fd8d9556 --- /dev/null +++ b/lib/network/src/URLResponse.hpp @@ -0,0 +1,33 @@ +#ifndef URLRESPONSE_HPP +#define URLRESPONSE_HPP + +#include + +namespace network { + struct URLResponse { + URLResponse(uint16_t statusCode, uint64_t responseBodySize) : statusCode(statusCode), responseBodySize(responseBodySize) {} + + /* + * The status code of the response + + For codes < 600 see online documentation, for codes >= 700 see the documentation below: + - 700: The URL is not valid + - 701: No internet connection + - 702: The request has been cancelled by the user + - 703: The cURL handle is null (only when WiFi is used) + - 704: The HTTP method is not supported + - 705: Timout when reading a chunk of the response data (GSM only) + - 706: Failed to send GET request (GSM only) + - 707: Failed to send POST request (GSM only) + - 708: The request has timed out (GSM only) + - 709: The request is cancelled or finished (GSM only) (should happen rarely) + */ + uint16_t statusCode; + + uint64_t responseBodySize; + + uint64_t readPosition = 0; + }; +} + +#endif // URLRESPONSE_HPP \ No newline at end of file diff --git a/lib/network/src/URLSession.cpp b/lib/network/src/URLSession.cpp new file mode 100644 index 00000000..d0f4e0af --- /dev/null +++ b/lib/network/src/URLSession.cpp @@ -0,0 +1,23 @@ +#include "URLSession.hpp" +#include "URLSessionDataTask.hpp" + +namespace network { + std::shared_ptr URLSession::defaultInstance/* = std::make_shared()*/; + + URLSession::URLSession() {} + + std::shared_ptr URLSession::dataTaskWithURL(const URL url, std::function task)> callback) + { + std::cout << "dataTaskWithURL" << std::endl; + URLRequest request(url); + return this->dataTaskWithRequest(request, callback); + } + + std::shared_ptr URLSession::dataTaskWithRequest(const URLRequest request, std::function task)> callback) + { + std::cout << "dataTaskWithRequest" << std::endl; + std::shared_ptr task = std::make_shared(URLSessionDataTask(request, this->preferWifi, callback)); + this->tasks.push_back(task); + return task; + } +} \ No newline at end of file diff --git a/lib/network/src/URLSession.hpp b/lib/network/src/URLSession.hpp new file mode 100644 index 00000000..4c86b86a --- /dev/null +++ b/lib/network/src/URLSession.hpp @@ -0,0 +1,26 @@ +#ifndef URLSESSION_HPP +#define URLSESSION_HPP + +#include +#include +#include +#include "URLSessionDataTask.hpp" + +namespace network { + class URLSession { + public: + static std::shared_ptr defaultInstance; + + bool preferWifi = false; + + bool concurrentRequestsLimit = 1; + + std::vector> tasks; + + URLSession(); + + std::shared_ptr dataTaskWithURL(const URL url, std::function task)> callback); + std::shared_ptr dataTaskWithRequest(const URLRequest, std::function task)> callback); + }; +} +#endif // URLSESSION_HPP \ No newline at end of file diff --git a/lib/network/src/URLSessionDataTask.cpp b/lib/network/src/URLSessionDataTask.cpp new file mode 100644 index 00000000..4623cc26 --- /dev/null +++ b/lib/network/src/URLSessionDataTask.cpp @@ -0,0 +1,278 @@ +#include "URLSessionDataTask.hpp" + +#include "NetworkManager.hpp" + +#include + +#include +#include + +namespace network { + URLSessionDataTask::URLSessionDataTask(const URLRequest request, bool useWiFi, std::function task)> callback) : URLSessionTask(request, useWiFi), callback(callback) + { + std::cout << "URLSessionDataTask" << std::endl; + std::cout << "useWiFi: " << int(useWiFi) << std::endl; + std::cout << "useWiFi2: " << int(this->useWiFi) << std::endl; + this->useWiFi = useWiFi; + + #ifdef ESP_PLATFORM + if (this->useWiFi) + { + curlHandle = curl_easy_init(); + } + #else + curlHandle = curl_easy_init(); + #endif + + if (!this->request.url.isValid()) + { + this->state = State::Cancelled; + this->handleResponse(700, 0); + return; + } + + #ifdef ESP_PLATFORM + if ((this->useWiFi && !NetworkManager::sharedInstance->isWiFiConnected()) || (!this->useWiFi && !NetworkManager::sharedInstance->isGSMConnected())) + { + std::cout << "No internet connection" << std::endl; + this->state = State::Cancelled; + this->handleResponse(701, 0); + return; + } + #else + if (!NetworkManager::sharedInstance->isWiFiConnected()) + { + std::cout << "No internet connection" << std::endl; + this->state = State::Cancelled; + this->handleResponse(701, 0); + return; + } + #endif + + #ifdef ESP_PLATFORM + delay(100); + std::cout << "\nuseWiFi3: " << int(useWiFi) << std::endl; + Serial.flush(); + + if (this->useWiFi) + { + std::cout << "sendRequestWiFi" << std::endl; + if (this->curlHandle == nullptr) + { + this->state = State::Cancelled; + this->handleResponse(703, 0); + return; + } + this->sendRequestWiFi(); + } + else + { + std::cout << "sendRequestGSM" << std::endl; + Serial.flush(); + this->sendRequestGSM(); + } + #else + this->sendRequestWiFi(); + #endif + }; + + void URLSessionDataTask::handleResponse(uint16_t statusCode, uint64_t responseBodySize) + { + if (this->response.has_value()) + return; // handlResponse already done + this->response = URLResponse(statusCode, responseBodySize); + + if (this->state != State::Cancelled) + this->state = State::ResponseReceived; + + if (this->useWiFi) + { + curl_easy_cleanup(this->curlHandle); + } + + this->callback(std::make_shared(*this)); + } + + void URLSessionDataTask::sendRequestGSM() + { + std::cout << "sendRequestGSM" << std::endl; + #ifdef ESP_PLATFORM + Serial.flush(); + #else + std::flush(std::cout); + #endif + GSM::sendRequest(std::make_shared(*this)); + } + + void URLSessionDataTask::sendRequestWiFi() + { + std::cout << "sendRequestWIFI" << std::endl; + + this->state = State::Running; + + switch (this->request.method) + { + case URLRequest::HTTPMethod::GET: + curl_easy_setopt(this->curlHandle, CURLOPT_HTTPGET, 1L); + break; + case URLRequest::HTTPMethod::POST: + curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDSIZE, this->request.httpBody.length()); + curl_easy_setopt(this->curlHandle, CURLOPT_POSTFIELDS, this->request.httpBody.c_str()); + break; + default: + this->state = State::Cancelled; + handleResponse(704, 0); + return; + } + curl_easy_setopt(this->curlHandle, CURLOPT_URL, this->request.url.absoluteString.c_str()); + curl_easy_setopt(this->curlHandle, CURLOPT_WRITEFUNCTION, this->WriteCallback); + curl_easy_setopt(this->curlHandle, CURLOPT_WRITEDATA, &this->responseData); + curl_easy_setopt(this->curlHandle, CURLOPT_TIMEOUT_MS, this->request.timeoutInterval); + + struct progress_s data = {this, 0}; /* pass struct to callback */ + + curl_easy_setopt(this->curlHandle, CURLOPT_XFERINFODATA, &data); + curl_easy_setopt(this->curlHandle, CURLOPT_XFERINFOFUNCTION, this->progress_callback); + + curl_easy_setopt(this->curlHandle, CURLOPT_NOPROGRESS, 0L); + + // set http headers + struct curl_slist* headers = nullptr; + for (const std::pair& header : this->request.httpHeaderFields) + { + headers = curl_slist_append(headers, (header.first + ": " + header.second).c_str()); + } + curl_easy_setopt(this->curlHandle, CURLOPT_HTTPHEADER, headers); + + CURLcode res = curl_easy_perform(this->curlHandle); + + if (res != CURLE_OK) + { + std::cerr << "cURL error: " << curl_easy_strerror(res) << std::endl; + } + + uint64_t httpCode = 0; + curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpCode); + + this->handleResponse(httpCode, this->responseData.length()); + } + + void URLSessionDataTask::resume() { + if (this->state == State::Paused) + { + if (this->useWiFi) + { + curl_easy_pause(this->curlHandle, CURLPAUSE_CONT); + } + else + { + //GSM::resumeRequest(); TODO + } + } + } + + void URLSessionDataTask::cancel() { + if (this->state == State::Running || this->state == State::Paused || this->state == State::Waiting || this->state == State::ResponseReceived) + { + this->state = State::Cancelled; + if (GSM::currentRequest.get() == this) + { + GSM::closeRequest(702); + } else { + // find the shared_ptr containing the current task and remove it + + auto it = std::find_if(GSM::hTTPRequests.begin(), GSM::hTTPRequests.end(), [this](std::shared_ptr task) { + return task.get() == this; + }); + + if (it != GSM::hTTPRequests.end()) + { + GSM::hTTPRequests.erase(it); + } + } + this->handleResponse(702, 0); // handle the response if it has not been done yet + } + } + + void URLSessionDataTask::pause() { // not working right now + if (this->state == State::Running) + { + if (this->useWiFi) + { + curl_easy_pause(this->curlHandle, CURLPAUSE_ALL); + } + else + { + //GSM::pauseRequest(); TODO + } + this->state = State::Paused; + } + } + + size_t URLSessionDataTask::WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) + { + size_t total_size = size * nmemb; + output->append(static_cast(contents), total_size); + return total_size; + } + + size_t URLSessionDataTask::progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) + { + struct progress_s *memory = static_cast(clientp); + + memory->task->countOfBytesReceived = dlnow; + memory->task->countOfBytesExpectedToReceive = dltotal; + memory->task->countOfBytesUploaded = ulnow; + memory->task->countOfBytesExpectedToUpload = ultotal; + + + if (dltotal == 0) + { + memory->task->progress = 0; + } else { + memory->task->progress = double(dlnow) / double(dltotal); + } + + if (memory->task->downloadProgressHandler) + { + memory->task->downloadProgressHandler(memory->task->progress); + } + + + if (ultotal == 0) + { + memory->task->uploadProgress = 0; + } else { + memory->task->uploadProgress = double(ulnow) / double(ultotal); + } + + if (memory->task->uploadProgressHandler) + { + memory->task->uploadProgressHandler(memory->task->uploadProgress); + } + return 0; /* all is good */ + } + + size_t URLSessionDataTask::readChunk(char* buffer) + { + if (this->state != State::ResponseReceived) + return 0; + + #ifdef ESP_PLATFORM + if (this->useWiFi) + { + if (this->responseData.empty()) + return 0; + return this->responseData.copy(buffer, this->response->responseBodySize); // TODO read only a chunk like GSM does + } + else + { + return GSM::readResponseDataChunk(buffer); + } + #else + if (this->responseData.empty()) + return 0; + return this->responseData.copy(buffer, this->response->responseBodySize); + #endif + } +} \ No newline at end of file diff --git a/lib/network/src/URLSessionDataTask.hpp b/lib/network/src/URLSessionDataTask.hpp new file mode 100644 index 00000000..3e4d405f --- /dev/null +++ b/lib/network/src/URLSessionDataTask.hpp @@ -0,0 +1,121 @@ +#ifndef URLSESSIONDATATASK_HPP +#define URLSESSIONDATATASK_HPP + +#include "URLSessionTask.hpp" +#include "URLRequest.hpp" +#include "ProgressHandler.hpp" +#include + +#include +#include +#include + + +namespace network { + class URLSessionDataTask : public URLSessionTask { + public: + URLSessionDataTask(const URLRequest request, bool useWiFi, std::function)> callback); + + bool useWiFi; + + void resume(); + + /* + * Cancel the task + + */ + void cancel(); + + /* + * Pause the task + + * @note Has no effect when useWifi is false + */ + void pause(); + + /* + * Handle the response of the request + + * @param statusCode The status code of the response + * @param responseBodySize The size of the response body + */ + void handleResponse(uint16_t statusCode, uint64_t responseBodySize); + + /* + * Read a chunk of the response data + + @param buffer The buffer to write the data to + @return The number of bytes read + + @note Will only work if the task is in the ResponseReceived state + */ + size_t readChunk(char* buffer); + + /* + * The response of the request + + @note Will be empty until a response from the server is received or an internal error (timout, invalid url, etc...) happens. + */ + std::optional response; + + + /* + * The progress of the download + + @note Will only work is useWiFi is true + */ + ProgressHandler downloadProgressHandler; + + /* + * The progress of the upload + + @note Will only work is useWiFi is true + */ + ProgressHandler uploadProgressHandler; + + /* + * The time when the task will timeout (in ms) + */ + uint64_t absoluteTimeout = -1; + + uint64_t countOfBytesUploaded = 0; // will increase in case of POST request + + uint64_t countOfBytesExpectedToUpload = 0; // will increase in case of POST request + + double uploadProgress = 0; + + /* + * The callback function to call when the task has received a response and you now can read the data or when an error occurs + */ + std::function task)> callback; + + private: + void sendRequestGSM(); + + void sendRequestWiFi(); + + CURL* curlHandle; + + std::string responseData; + + /* + * Callback function for cURL to write the response data + */ + static size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output); + + struct progress_s { + URLSessionDataTask* task; + size_t size; + }; + + /* + * Callback function for cURL to update the progress + + @note This function is called by cURL and represents the progress of the data transfer. When using GSM (i.e. useWiFi is false), + the progress is defined by the size of the response body and the readPosition of the response (so no need for a progress callback). + */ + static size_t progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); + }; +} + +#endif // URLSESSIONDATATASK_HPP \ No newline at end of file diff --git a/lib/network/src/URLSessionDownloadTask.hpp b/lib/network/src/URLSessionDownloadTask.hpp new file mode 100644 index 00000000..391ce068 --- /dev/null +++ b/lib/network/src/URLSessionDownloadTask.hpp @@ -0,0 +1,8 @@ +#ifndef URLSESSIONDOWNLOADTASK_HPP +#define URLSESSIONDOWNLOADTASK_HPP + +namespace network { + +} + +#endif // URLSESSIONDDOWNLOADTASK_HPP \ No newline at end of file diff --git a/lib/network/src/URLSessionTask.hpp b/lib/network/src/URLSessionTask.hpp new file mode 100644 index 00000000..c2a85c56 --- /dev/null +++ b/lib/network/src/URLSessionTask.hpp @@ -0,0 +1,88 @@ +#ifndef URLSESSIONTASK_HPP +#define URLSESSIONTASK_HPP + +#include "URLRequest.hpp" +#include "URLResponse.hpp" +#include +#include +#include + +namespace network { + class URLSessionTask { + public: + enum State { + /* + * The task is waiting to be launched + */ + Waiting, + + /* + * The task is sending/has been sent to the server + */ + Running, + + /* + * The task has been cancelled by the user or by the system because of an internal error (see return code) + */ + Cancelled, + + /* + * The task has been paused by the user, not effective when useWiFi is false + */ + Paused, + + /* + * The task has received a response from the server, you can read the response in the `response` property + */ + ResponseReceived, + + /* + * The task has finished, no data can't be read anymore (except when useWiFi is true) + */ + Finished + }; + + const URLRequest request; + + /* + * The response of the request + + @note Will be empty until a response from the server is received or an internal error (timout, invalid url, etc...) happens. + */ + std::optional response; + + /* + * Boolean indicating whether the task is using WiFi or GSM + */ + const bool useWiFi; + + virtual void resume() = 0; + + virtual void cancel() = 0; + + virtual void pause() = 0; + + State state = State::Waiting; + + double progress = 0.0; + + uint64_t countOfBytesReceived = 0; + uint64_t countOfBytesExpectedToReceive = 0; // total bytes expected to be received + + void updateTimeout() { + this->absoluteTimeout = millis() + this->request.timeoutInterval; + } + + bool isOverTimeout() { + return millis() > this->absoluteTimeout && this->absoluteTimeout != -1; + } + protected: + URLSessionTask(const URLRequest request, bool useWiFi) : request(request), useWiFi(useWiFi) {}; + + virtual ~URLSessionTask() { } + + uint64_t absoluteTimeout = -1; + }; +} + +#endif // URLSESSIONTASK_HPP \ No newline at end of file diff --git a/lib/network/src/URLSessionUploadTask.hpp b/lib/network/src/URLSessionUploadTask.hpp new file mode 100644 index 00000000..d219465e --- /dev/null +++ b/lib/network/src/URLSessionUploadTask.hpp @@ -0,0 +1,8 @@ +#ifndef URLSESSIONUPLOADTASK_HPP +#define URLSESSIONUPLOADTASK_HPP + +namespace network { + +} + +#endif // URLSESSIONUPLOADTASK_HPP \ No newline at end of file diff --git a/lib/network/src/URLSessionWebSocketTask.hpp b/lib/network/src/URLSessionWebSocketTask.hpp new file mode 100644 index 00000000..a08b6a45 --- /dev/null +++ b/lib/network/src/URLSessionWebSocketTask.hpp @@ -0,0 +1,8 @@ +#ifndef URLSESSIONWEBSOCKETTASK_HPP +#define URLSESSIONWEBSOCKETTASK_HPP + +namespace network { + +} + +#endif // URLSESSIONWEBSOCKETTASK_HPP \ No newline at end of file diff --git a/lib/tasks/src/threads.cpp b/lib/tasks/src/threads.cpp index c0948e1a..07b3c5a6 100644 --- a/lib/tasks/src/threads.cpp +++ b/lib/tasks/src/threads.cpp @@ -9,7 +9,7 @@ EventHandler eventHandlerApp; #endif -#ifdef ESP32 +#ifdef ESP_PLATFORM #include #include "soc/rtc_wdt.h" #include "esp_heap_caps.h" diff --git a/platformio.ini b/platformio.ini index 3e99163a..8901eddf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,23 +18,30 @@ test_framework = googletest platform = espressif32 board = esp-wrover-kit framework = arduino -board_build.partitions = huge_app.csv +;board_build.partitions = huge_app.csv +;board_build.partitions = partition.csv +;build_type = release +board_upload.flash_size = 16MB +board_build.partitions = default_16MB.csv monitor_speed = 115200 lib_ldf_mode = chain+ lib_deps = lovyan03/LovyanGFX@^1.1.9 bitbank2/FT6236G@^1.0.0 + anto/libcurl-esp32 anto/backtrace-saver-esp32@1.1.0 build_flags = -std=gnu++17 -DPLATFORM_PAXO_V5 + -DBUILDING_LIBCURL + -DHAVE_CONFIG_H -DCONFIG_FATFS_MAX_LFN=255 -I include -fexceptions -Wno-error=maybe-uninitialized - -Wno-reorder + ;-Wno-reorder -DBOARD_HAS_PSRAM - ;-mfix-esp32-psram-cache-issue + -mfix-esp32-psram-cache-issue -DCONFIG_SPIRAM_CACHE_WORKAROUND -DCORE_DEBUG_LEVEL=5 -DSOL_NO_THREAD_LOCAL=1 @@ -63,6 +70,9 @@ build_flags = -lm -Iextern/SDL2-2.28.5/x86_64-w64-mingw32/include/SDL2 -Lextern/SDL2-2.28.5/x86_64-w64-mingw32/lib + -Iextern/curl-8.7.1_8-win64-mingw/include + -Lextern/curl-8.7.1_8-win64-mingw/lib + -lcurl -lSDL2main -lSDL2 -m64 @@ -73,6 +83,7 @@ extra_scripts = pre:scripts/platformio/windows/setup_workspace.py pre:scripts/platformio/windows/copy_dependencies.py scripts/platformio/windows/execute.py +targets = execute [env:windows-build-only] platform = native @@ -89,6 +100,9 @@ build_flags = -lm -Iextern/SDL2-2.28.5/x86_64-w64-mingw32/include/SDL2 -Lextern/SDL2-2.28.5/x86_64-w64-mingw32/lib + -Iextern/curl-8.7.1_8-win64-mingw/include + -Lextern/curl-8.7.1_8-win64-mingw/lib + -lcurl -lSDL2main -lSDL2 -m64 @@ -115,6 +129,7 @@ build_flags = -lm -lSDL2main -lSDL2 + -lcurl -g -ggdb @@ -134,11 +149,13 @@ build_flags = -lm -lSDL2main -lSDL2 - -I/usr/local/include/SDL2 + -lcurl + -I/usr/local/include/SDL2 # we include both /usr/local/ and /opt/homebrew/ to support the macOS silicon and Intel as explained here https://stackoverflow.com/a/71186857/16456439 -L/usr/local/lib -I/opt/homebrew/include/SDL2 -L/opt/homebrew/lib + [env:test] platform = native test_framework = googletest diff --git a/scripts/platformio/windows/copy_dependencies.py b/scripts/platformio/windows/copy_dependencies.py index 1671143c..c7ecf785 100644 --- a/scripts/platformio/windows/copy_dependencies.py +++ b/scripts/platformio/windows/copy_dependencies.py @@ -12,9 +12,13 @@ def SDL2_copyDLL(): build_dir = env.subst("$BUILD_DIR") - # Copy SDL2.dll - if not os.path.exists(f"{build_dir}\\SDL2.dll"): - SDL2_copyDLL() + +def cURL_copyDLL(): + build_dir = env.subst("$BUILD_DIR") + + shutil.copyfile("extern\\curl-8.7.1_8-win64-mingw\\bin\\libcurl-x64.dll", f"{build_dir}\\libcurl-x64.dll") + + build_dir = env.subst("$BUILD_DIR") def copy_dependencies(): @@ -24,6 +28,10 @@ def copy_dependencies(): if not os.path.exists(f"{build_dir}\\SDL2.dll"): SDL2_copyDLL() + # Copy libcurl-x64.dll + if not os.path.exists(f"{build_dir}\\libcurl-x64.dll"): + cURL_copyDLL() + # Execute "copy_dependencies" before building program env.AddPreAction("buildprog", copy_dependencies) diff --git a/scripts/platformio/windows/setup_workspace.py b/scripts/platformio/windows/setup_workspace.py index f814517c..cfe82630 100644 --- a/scripts/platformio/windows/setup_workspace.py +++ b/scripts/platformio/windows/setup_workspace.py @@ -7,16 +7,17 @@ def SDL2_cleanup(): + """ + Remove artifacts generated from "SDL2_download" + """ + try: - if os.path.exists("temp"): - if os.path.exists("temp\\SDL2.zip"): - os.remove("temp\\SDL2.zip") - os.remove("temp") + if os.path.exists("temp\\SDL2.zip"): + os.remove("temp\\SDL2.zip") except PermissionError: - print("Could not delete temp directory or the SDL2.zip file. Please delete them manually.") + print("Could not delete temp/SDL2.zip. Please delete them manually.") - def SDL2_download(): """ Download SDL2 @@ -29,6 +30,9 @@ def SDL2_download(): def SDL2_extract(): + """ + Extract SDL2 to the "extern" directory + """ with zipfile.ZipFile("temp\\SDL2.zip", "r") as zipFile: zipFile.extractall("extern") @@ -46,9 +50,67 @@ def SDL2_copyDLL(): shutil.copyfile("extern\\SDL2-2.28.5\\x86_64-w64-mingw32\\bin\\SDL2.dll", f"{build_dir}\\SDL2.dll") + +def cURL_cleanup(): + """ + Remove artifacts generated from "cURL_download" + """ + + try: + if os.path.exists("temp\\cURL.zip"): + os.remove("temp\\cURL.zip") + except PermissionError: + print("Could not delete temp/cURL.zip. Please delete them manually.") + + +def cURL_download(): + """ + Download cURL + """ + + r = requests.get("https://curl.se/windows/dl-8.7.1_8/curl-8.7.1_8-win64-mingw.zip") + + with open("temp\\cURL.zip", "wb") as file: + file.write(r.content) + + +def cURL_extract(): + """ + Extract cURL to the "extern" directory + """ + with zipfile.ZipFile("temp\\cURL.zip", "r") as zipFile: + zipFile.extractall("extern") + + +def cURL_exists(): + """ + :return: true, if cURL is installed, false, if cURL is not installed + """ + + return os.path.exists("extern") and os.path.exists("extern\\curl-8.7.1_8-win64-mingw") + + +def cURL_copyDLL(): + build_dir = env.subst("$BUILD_DIR") + + shutil.copyfile("extern\\curl-8.7.1_8-win64-mingw\\bin\\libcurl-x64.dll", f"{build_dir}\\libcurl-x64.dll") + + +def cleanup(): + """ + Cleanup + """ + + try: + if os.path.exists("temp"): + os.rmdir("temp") + except PermissionError: + print("Could not delete temp directory. Please delete it manually.") + + def setup_workspace(): """ - Setup the workspace (Download SDL2, copy .dll, ...) + Set up the workspace (Download SDL2, copy .dll, ...) """ build_dir = env.subst("$BUILD_DIR") @@ -57,6 +119,10 @@ def setup_workspace(): if not os.path.exists("extern"): os.mkdir("extern") + # Create a temp directory + if not os.path.exists("temp"): + os.mkdir("temp") + # Download and setup SDL2 if not SDL2_exists(): # Create a temp directory @@ -67,10 +133,23 @@ def setup_workspace(): SDL2_extract() SDL2_cleanup() + # Download and setup cURL + if not cURL_exists(): + cURL_download() + cURL_extract() + cURL_cleanup() + + # Cleanup + cleanup() + # Copy SDL2.dll if not os.path.exists(f"{build_dir}\\SDL2.dll"): SDL2_copyDLL() + # Copy libcurl-x64.dll + if not os.path.exists(f"{build_dir}\\libcurl-x64.dll"): + cURL_copyDLL() + -# Setup the workspace when importing the project +# Set up the workspace when importing the project setup_workspace() diff --git a/src/main.cpp b/src/main.cpp index 2f5c3681..bd0b3dcb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,8 @@ SET_LOOP_TASK_STACK_SIZE(16 * 1024); #include #include #include +#include +#include <../network/network.hpp> using namespace gui::elements; @@ -276,7 +278,7 @@ void setup() }; #ifdef ESP_PLATFORM - ThreadManager::new_thread(CORE_BACK, &ringingVibrator, 16000); + ThreadManager::new_thread(CORE_BACK, &ringingVibrator, 4*1024); #endif // gestion de la détection du toucher de l'écran @@ -295,6 +297,31 @@ void setup() std::vector cc = Contacts::listContacts(); AppManager::init(); + network::init(); + + PaxOS_Delay(5000); + network::URLSession::defaultInstance->dataTaskWithURL(std::string("https://www.example.com/"), [](std::shared_ptr task) { + char data[2048]; + std::cout << "Request done, code: " << task->response->statusCode << ", dataSize: " << task->response->responseBodySize << std::endl; + + if (task->response == std::nullopt) { + std::cout << "Request failed" << std::endl; + return; + } else { + std::cout << "Request succeeded" << std::endl; + size_t size = task->readChunk(data); + std::cout << "Response size: " << size << std::endl; + + for (size_t i = 0; i < size; ++i) + { + if (data[i] < 32 || data[i] > 126) + std::cout << '.'; + else + std::cout << data[i]; + } + std::cout << std::endl; + } + }); mainLoop(NULL); }