diff --git a/.gitignore b/.gitignore index 53072ca..1fe39c9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +.idea/ # Aptatio/Platformio specifics secrets.ini diff --git a/config.ini b/config.ini index 11e4023..aa11b29 100644 --- a/config.ini +++ b/config.ini @@ -10,6 +10,7 @@ monitor_speed = 115200 ; notworthy ones: ; __PLATFORMIO_BUILD_DEBUG__ = debug mode build_flags = + -std=c++17 ; DO NOT TOUCH --- START -D MONITOR_SPEED=${config.monitor_speed} ; DO NOT TOUCH --- END diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4fa2e04 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +version: "3" + +services: + mariadb: + image: mariadb:latest + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: dolibarr + + web: + image: tuxgasy/dolibarr + environment: + DOLI_DB_HOST: mariadb + DOLI_DB_USER: root + DOLI_DB_PASSWORD: root + DOLI_DB_NAME: dolibarr + DOLI_URL_ROOT: 'http://0.0.0.0' + PHP_INI_DATE_TIMEZONE: 'Europe/Paris' + ports: + - "80:80" + links: + - mariadb diff --git a/include/Program.h b/include/Program.h index 66e4a79..822f6b3 100644 --- a/include/Program.h +++ b/include/Program.h @@ -1,7 +1,10 @@ #ifndef PROGRAM_H #define PROGRAM_H +#define DEFAULT_BAUD_RATE 115200 + #include "Arduino.h" +#include "DolibarrClient.h" class Program { public: @@ -11,9 +14,11 @@ public: Program(); /** - * Program main loop + * Program WarehouseGUI loop */ void loop(); +private: + DolibarrClient *client; }; #endif diff --git a/lib/DolibarrClient/src/DolibarrClient.cpp b/lib/DolibarrClient/src/DolibarrClient.cpp new file mode 100644 index 0000000..b066325 --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrClient.cpp @@ -0,0 +1,170 @@ +#include "DolibarrClient.h" +#include +#include +#include + +#define WAITING_WIFI_DELAY 1000 + +DolibarrClient::DolibarrClient(struct WifiConfig wifi_config, struct DolibarrConfig dolibarr_config) : wifi(wifi_config), dolibarr(dolibarr_config) { + #if defined(DEBUG) + Serial.println(" --- Wifi configuration --- "); + Serial.println((std::string("SSID: ") + std::string(wifi_config.ssid)).c_str()); + Serial.println((std::string("Password: ") + std::string(wifi_config.password)).c_str()); + Serial.println(" --- Dolibarr configuration --- "); + Serial.println((std::string("Base URL: ") + std::string(dolibarr_config.url)).c_str()); + Serial.println((std::string("Token: ") + std::string(dolibarr_config.api_key)).c_str()); + #endif + this->initialize_wifi(); + this->initialize_http_client(); +} + +HTTPClient *DolibarrClient::build_url(const String& url) const { + auto *client = new HTTPClient(); + String clientUrl = this->dolibarr.url + url; + client->begin(clientUrl); + client->addHeader("Content-Type", "application/json"); + client->addHeader("DOLAPIKEY", this->dolibarr.api_key); + #if defined(DEBUG) + Serial.println("URL Request: " + clientUrl); + #endif + return client; +} + + +int DolibarrClient::login() const { + HTTPClient *client = this->build_url(LOGIN_URL); + int httpResponseCode = client->GET(); + if (httpResponseCode > 0) { + StaticJsonDocument doc; + DeserializationError error = deserializeJson(doc, client->getString().c_str()); + if (error) { + delete client; + return -1; + } + delete client; + return 0; + } + delete client; + return -1; +} + +std::string replace_id(const char *str, const char *id) { + std::string url(str); + url.replace(url.find("{id}"), 4, id); + return url; +} + +std::vector *DolibarrClient::list_products() const { + HTTPClient *client = this->build_url(LIST_PRODUCT_URL); + if (client->GET() == OK_RESPONSE) { + + } + return nullptr; +} + +models::Product *DolibarrClient::get_product_by_id(const char* id_product) const { + HTTPClient *client = this->build_url(replace_id(GET_PRODUCT_URL, id_product).c_str()); + if (client->GET() == OK_RESPONSE) { + StaticJsonDocument doc; + DeserializationError error = deserializeJson(doc, client->getString().c_str()); + if (error) { + Serial.println("ERROR: "); + Serial.println(error.c_str()); + delete client; + return nullptr; + } + auto *product = new models::Product(); + product->date_creation = doc["date_creation"]; + product->id = doc["id"]; + product->entity = doc["entity"]; + product->stock_reel = doc["stock_reel"]; + product->label = doc["label"]; + delete client; + return product; + } + delete client; + return nullptr; +} + +std::vector *DolibarrClient::list_warehouse() const { + HTTPClient *client = this->build_url(LIST_WAREHOUSE_URL); + if (client->GET() == OK_RESPONSE) { + StaticJsonDocument doc; + DeserializationError error = deserializeJson(doc, client->getString().c_str()); + if (error) { + Serial.println("ERROR: "); + Serial.println(error.c_str()); + delete client; + return nullptr; + } + auto *warehouses = new std::vector(); + for (auto obj : doc.as()) { + models::Warehouse warehouse = {}; + warehouse.id = obj["id"]; + warehouses->push_back(warehouse); + } + delete client; + return warehouses; + } + delete client; + return nullptr; +} + +int DolibarrClient::create_movement(models::CreateProductStock &stock) const { + HTTPClient *client = this->build_url(CREATE_STOCKS_MOVEMENTS_URL); + StaticJsonDocument doc; + std::string result; + doc["product_id"] = stock.product_id; + doc["warehouse_id"] = stock.warehouse_id; + doc["qty"] = stock.qty; + serializeJson(doc, result); + Serial.println(result.c_str()); + if (client->POST(result.c_str()) == OK_RESPONSE) { + delete client; + return 0; + } + return -1; +} + +int DolibarrClient::initialize_wifi() const { + WiFiClass::mode(WIFI_STA); //Optional + WiFi.setSleep(false); + WiFi.begin(this->wifi.ssid, this->wifi.password); + Serial.print("Connecting "); + while(WiFiClass::status() != WL_CONNECTED){ + delay(WAITING_WIFI_DELAY); + Serial.print("."); + } + Serial.println("Connected to the WiFi network"); + return 0; +} + +int DolibarrClient::initialize_http_client() { + this->httpClient = new HTTPClient(); + if (this->login() == 0) { + auto* product = this->get_product_by_id("1"); + if (product == nullptr) { + Serial.println("Product is nullptr !"); + return -1; + } + Serial.println("Product label: "); + Serial.println(product->label); + auto* warehouses = this->list_warehouse(); + if (warehouses == nullptr) { + delete product; + Serial.println("Warehouse is nullptr !"); + return -1; + } + Serial.println("Warehouses: "); + models::CreateProductStock product_stock = {product->id, warehouses->at(0).id, "1"}; + this->create_movement(product_stock); + for (auto warehouse : *warehouses) { + Serial.println(warehouse.id); + } + delete product; + delete warehouses; + } else { + Serial.println("An Error has occurred while trying to login"); + } + return 0; +} \ No newline at end of file diff --git a/lib/DolibarrClient/src/DolibarrClient.h b/lib/DolibarrClient/src/DolibarrClient.h new file mode 100644 index 0000000..82cc058 --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrClient.h @@ -0,0 +1,56 @@ +#ifndef DOLIBARR_CLIENT_H +#define DOLIBARR_CLIENT_H + +#include +#include +#include +#include "DolibarrModels.h" + +constexpr u_int32_t OK_RESPONSE = 200; +constexpr u_int32_t CREATED_RESPONSE = 201; +constexpr u_int32_t NO_CONTENT_RESPONSE = 204; + +constexpr u_int32_t LOGIN_JSON_SIZE = 4096; +constexpr const char* LOGIN_URL = "/users/info"; +constexpr u_int32_t LIST_PRODUCT_JSON_SIZE = 4096; +constexpr const char* LIST_PRODUCT_URL = "/products/"; +constexpr u_int32_t GET_PRODUCT_JSON_SIZE = 4096; +constexpr const char* GET_PRODUCT_URL = "/products/{id}/"; +constexpr u_int32_t GET_PRODUCT_STOCK_JSON_SIZE = 4096; +constexpr const char* GET_PRODUCT_STOCK_URL = "/products/{id}/stock/"; +constexpr u_int32_t LIST_WAREHOUSE_JSON_SIZE = 4096; +constexpr const char* LIST_WAREHOUSE_URL = "/warehouses/"; +constexpr u_int32_t LIST_STOCKS_MOVEMENTS_JSON_SIZE = 4096; +constexpr const char* LIST_STOCKS_MOVEMENTS_URL = "/stockmovements/?sortfield=t.rowid&sortorder=ASC&limit=100"; +constexpr u_int32_t CREATE_STOCKS_MOVEMENTS_JSON_SIZE = 4096; +constexpr const char* CREATE_STOCKS_MOVEMENTS_URL = "/stockmovements/"; + +struct WifiConfig { + const char* ssid; + const char* password; +}; + +struct DolibarrConfig { + const char* url; + const char* api_key; +}; + +class DolibarrClient { +public: + DolibarrClient(WifiConfig wifi_config, DolibarrConfig dolibarr_config); + ~DolibarrClient() = default; + int login() const; + std::vector *list_warehouse() const; + std::vector *list_products() const; + models::Product *get_product_by_id(const char* id_product) const; + int create_movement(models::CreateProductStock &stock) const; +private: + HTTPClient* httpClient{}; + struct WifiConfig wifi; + struct DolibarrConfig dolibarr; + int initialize_wifi() const; + int initialize_http_client(); + HTTPClient *build_url(const String& url) const; +}; + +#endif //DOLIBARR_CLIENT_H diff --git a/lib/DolibarrClient/src/DolibarrModels.cpp b/lib/DolibarrClient/src/DolibarrModels.cpp new file mode 100644 index 0000000..822e44e --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrModels.cpp @@ -0,0 +1,5 @@ +// +// Created by nico on 15/09/23. +// + +#include "DolibarrModels.h" diff --git a/lib/DolibarrClient/src/DolibarrModels.h b/lib/DolibarrClient/src/DolibarrModels.h new file mode 100644 index 0000000..1443119 --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrModels.h @@ -0,0 +1,40 @@ +#ifndef T_IOT_901_CONVOYOR_DOLIBARRMODELS_H +#define T_IOT_901_CONVOYOR_DOLIBARRMODELS_H + +namespace models { + + struct Product { + const char* id; + const char* entity; + const char* ref; + const char* status; + const char* date_creation; + const char* date_modification; + const char* label; + const char* description; + const char* type; + const char* price; + const char* stock_reel; + const char* seuil_stock_alerte; + const char* desiredstock; + }; + + struct ProductStock { + const char* id; + const char* product_id; + const char* quantity; + }; + + struct CreateProductStock { + const char* product_id; + const char* warehouse_id; + const char* qty; + }; + + struct Warehouse { + const char* id; + }; + +} + +#endif //T_IOT_901_CONVOYOR_DOLIBARRMODELS_H diff --git a/lib/WarehouseGUI/src/GUIScreen.cpp b/lib/WarehouseGUI/src/GUIScreen.cpp new file mode 100644 index 0000000..3c3691b --- /dev/null +++ b/lib/WarehouseGUI/src/GUIScreen.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "GUIScreen.h" + +// Abstract AGuiScreen definition + +gui::AGUIScreen::AGUIScreen() { + std::cout << "Hello form AGUIScreen" << "\n"; + this->widgets = std::vector(); +} + +int gui::AGUIScreen::update() { + std::sort(widgets.begin(), widgets.end(), [](AGUIWidget* aWidget, AGUIWidget* bWidget) { + return aWidget->getLayer() < bWidget->getLayer(); + }); + for (auto *widget: widgets) { + widget->update(); + } + return (0); +} + +int gui::AGUIScreen::addWidget(gui::AGUIWidget *widget) { + this->widgets.push_back(widget); + return 0; +} + +int gui::AGUIScreen::removeWidget(gui::AGUIWidget *_widget) { + return 0; +} + +int gui::AGUIScreen::removeWidget(int _index) { + return 0; +} + +int gui::AGUIScreen::removeWidget(const char *_name) { + return 0; +} + +std::vector gui::AGUIScreen::getWidgets() const { + return this->widgets; +} + + +// DefaultGuiScreen definition + +gui::DefaultGuiScreen::DefaultGuiScreen() { + std::cout << "Hello form DefaultGuiScreen" << "\n"; +} + +int gui::DefaultGuiScreen::setup() { + return 0; +} + +const char *gui::DefaultGuiScreen::getName() const { + return "DefaultGuiScreen"; +} diff --git a/lib/WarehouseGUI/src/GUIScreen.h b/lib/WarehouseGUI/src/GUIScreen.h new file mode 100644 index 0000000..3c022bc --- /dev/null +++ b/lib/WarehouseGUI/src/GUIScreen.h @@ -0,0 +1,35 @@ +#ifndef T_IOT_901_CONVOYOR_GUICONTAINER_H +#define T_IOT_901_CONVOYOR_GUICONTAINER_H + +#include +#include "GUIWidget.h" + +namespace gui { + + class AGUIScreen { + public: + AGUIScreen(); + ~AGUIScreen() = default; + virtual const char* getName() const = 0; + virtual int setup() = 0; + int update(); + int addWidget(AGUIWidget* widget); + int removeWidget(AGUIWidget* widget); + int removeWidget(int index); + int removeWidget(const char* name); + std::vector getWidgets() const; + protected: + std::vector widgets; + }; + + class DefaultGuiScreen : public AGUIScreen { + public: + DefaultGuiScreen(); + ~DefaultGuiScreen() = default; + const char* getName() const override; + int setup() override; + }; + +} + +#endif //T_IOT_901_CONVOYOR_GUICONTAINER_H diff --git a/lib/WarehouseGUI/src/GUIWidget.cpp b/lib/WarehouseGUI/src/GUIWidget.cpp new file mode 100644 index 0000000..d820330 --- /dev/null +++ b/lib/WarehouseGUI/src/GUIWidget.cpp @@ -0,0 +1,33 @@ +#include "GUIWidget.h" + +gui::AGUIWidget::AGUIWidget(int x, int y, int width, int height, int layer) : layer(layer) { + this->position.x = x; + this->position.y = y; + this->size.width = width; + this->size.height = height; +} + +int gui::AGUIWidget::getLayer() const { + return this->layer; +} + +void gui::AGUIWidget::setLayer(int newLayer) { + this->layer = newLayer; +} + +GuiWidgetPosition gui::AGUIWidget::getPosition() const { + return this->position; +} + +void gui::AGUIWidget::setPosition(GuiWidgetPosition pos) { + this->position = pos; +} + +GuiWidgetSize gui::AGUIWidget::getSize() const { + return this->size; +} + +void gui::AGUIWidget::setSize(GuiWidgetSize newSize) { + this->size = newSize; +} + diff --git a/lib/WarehouseGUI/src/GUIWidget.h b/lib/WarehouseGUI/src/GUIWidget.h new file mode 100644 index 0000000..d6f1b8c --- /dev/null +++ b/lib/WarehouseGUI/src/GUIWidget.h @@ -0,0 +1,35 @@ +#ifndef T_IOT_901_CONVOYOR_GUIWIDGET_H +#define T_IOT_901_CONVOYOR_GUIWIDGET_H + +struct GuiWidgetPosition { + int x; + int y; +}; + +struct GuiWidgetSize { + int width; + int height; +}; + +namespace gui { + class AGUIWidget { + public: + AGUIWidget(int x, int y, int width, int height, int layer = 0); + ~AGUIWidget() = default; + virtual const char* getName() const = 0; + virtual void setup() = 0; + virtual void update() = 0; + int getLayer() const; + void setLayer(int layer); + GuiWidgetPosition getPosition() const; + void setPosition(GuiWidgetPosition position); + GuiWidgetSize getSize() const; + void setSize(GuiWidgetSize size); + private: + GuiWidgetPosition position{}; + GuiWidgetSize size{}; + int layer; + }; +} + +#endif //T_IOT_901_CONVOYOR_GUIWIDGET_H diff --git a/lib/WarehouseGUI/src/WarehouseGUI.cpp b/lib/WarehouseGUI/src/WarehouseGUI.cpp new file mode 100644 index 0000000..407db1f --- /dev/null +++ b/lib/WarehouseGUI/src/WarehouseGUI.cpp @@ -0,0 +1,82 @@ +#include "WarehouseGUI.h" +#include "M5Stack.h" + +using namespace gui; + +WarehouseGUI::WarehouseGUI() { + this->screens = std::vector(); +} + +WarehouseGUI::~WarehouseGUI() = default; + +int WarehouseGUI::addScreens(gui::AGUIScreen *screen) { + this->screens.push_back(screen); + return 0; +} + +int WarehouseGUI::removeScreens(gui::AGUIScreen *screen) { + for (int i = 0; i < this->screens.size(); ++i) { + if (this->screens[i] == screen) { + this->screens.erase(this->screens.begin() + i); + return 0; + } + } + return -1; +} + +int WarehouseGUI::removeScreens(int index) { + if (index < 0 || index >= this->screens.size()) { + return -1; + } + this->screens.erase(this->screens.begin() + index); + return 0; +} + +int WarehouseGUI::removeScreens(const char *name) { + for (int i = 0; i < this->screens.size(); ++i) { + if (this->screens[i]->getName() == name) { + this->screens.erase(this->screens.begin() + i); + return 0; + } + } + return -1; +} + +int WarehouseGUI::update() { + return this->current_screen->update(); +} + +int WarehouseGUI::setup() { + if (this->current_screen == nullptr) { + return -1; + } + return this->current_screen->setup(); +} + +int WarehouseGUI::changeCurrentScreen(const char *name) { + for (auto *screen : this->screens) { + if (screen->getName() == name) { + this->current_screen = screen; + this->current_screen->setup(); + return 0; + } + } + return -1; +} + +int WarehouseGUI::changeCurrentScreen(int index) { + if (index < 0 || index >= this->screens.size()) { + return -1; + } + this->current_screen = this->screens[index]; + this->current_screen->setup(); + return 0; +} + +std::vector gui::WarehouseGUI::getAllScreens() { + return this->screens; +} + +AGUIScreen *gui::WarehouseGUI::getCurrentScreen() { + return this->current_screen; +} diff --git a/lib/WarehouseGUI/src/WarehouseGUI.h b/lib/WarehouseGUI/src/WarehouseGUI.h new file mode 100644 index 0000000..9bedc4d --- /dev/null +++ b/lib/WarehouseGUI/src/WarehouseGUI.h @@ -0,0 +1,29 @@ +#ifndef T_IOT_901_CONVOYOR_WAREHOUSEGUI_H +#define T_IOT_901_CONVOYOR_WAREHOUSEGUI_H + + +#include +#include "GUIScreen.h" + +namespace gui { + class WarehouseGUI { + public: + WarehouseGUI(); + ~WarehouseGUI(); + int addScreens(AGUIScreen* screen); + int removeScreens(AGUIScreen* screen); + int removeScreens(int index); + int removeScreens(const char *name); + int changeCurrentScreen(const char* name); + int changeCurrentScreen(int index); + std::vector getAllScreens(); + AGUIScreen* getCurrentScreen(); + int setup(); + int update(); + private: + std::vector screens; + AGUIScreen *current_screen{nullptr}; + }; +} // namespace gui + +#endif //T_IOT_901_CONVOYOR_WAREHOUSEGUI_H diff --git a/platformio.ini b/platformio.ini index 95b9ed1..94d4fa2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,6 +50,9 @@ upload_speed = 921600 ; librairies (make sure to fix versions where possible!) lib_deps = + bblanchon/ArduinoJson@^6.21.3 + m5stack/M5Stack@^0.4.5 + m5stack/M5GFX@^0.1.9 ; example: ; erropix/ESP32 AnalogWrite@0.2 diff --git a/secrets.ini.example b/secrets.ini.example index 90b9782..5df2d8a 100644 --- a/secrets.ini.example +++ b/secrets.ini.example @@ -4,3 +4,8 @@ [secrets] build_flags = + -D WIFI_SSID=\"test\" + -D WIFI_PASSWORD=\"abcd1234\" + -D DOLIBARR_API_TOKEN=\"monapitokendedolibarr\" + -D DOLIBARR_URL=\"http://0.0.0.0/api/index.php\" + -D DEBUG=true \ No newline at end of file diff --git a/src/Program.cpp b/src/Program.cpp index e412959..7d7c236 100644 --- a/src/Program.cpp +++ b/src/Program.cpp @@ -1,10 +1,12 @@ #include "Program.h" +#include "Arduino.h" +#include "DolibarrClient.h" Program::Program() { - // Startup - Serial.begin(MONITOR_SPEED); + struct WifiConfig wifi_c = {WIFI_SSID, WIFI_PASSWORD}; + struct DolibarrConfig dolibarr = {DOLIBARR_URL, DOLIBARR_API_TOKEN}; + this->client = new DolibarrClient(wifi_c, dolibarr); } void Program::loop() { - // Loop } diff --git a/src/main.cpp b/src/main.cpp index 74bf5a7..e613919 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,19 @@ #include "Program.h" +#include "Arduino.h" +#include "WarehouseGUI.h" +#include Program* program; void setup() { + + Serial.begin(DEFAULT_BAUD_RATE); program = new Program(); + + #if defined(DEBUG) + Serial.println("Hello World"); + #endif + } void loop() { diff --git a/test/dolibarr.cpp b/test/dolibarr.cpp new file mode 100644 index 0000000..07a1edb --- /dev/null +++ b/test/dolibarr.cpp @@ -0,0 +1,10 @@ +#include +#include "DolibarrClient.h" + +void test_construct_dolibarr_client() { + return; +} + +void test_destroy_basic_state() { + return; +} diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..ef919f7 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "test.h" + +void setUp(void) { + // set stuff up here +} + +void tearDown(void) { + // clean stuff up here +} + +int runUnityTests(void) { + UNITY_BEGIN(); + RUN_TEST(test_construct_dolibarr_client); + return UNITY_END(); +} + +int main(void) { + return runUnityTests(); +} + +// For Arduino framework +void setup() { + // Wait ~2 seconds before the Unity test runner + // establishes connection with a board Serial interface + runUnityTests(); +} + +// For Arduino framework +void loop() {} + +// For ESP-IDF framework +void app_main() { + runUnityTests(); +} diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..95c9ea5 --- /dev/null +++ b/test/test.h @@ -0,0 +1,6 @@ +#ifndef T_IOT_901_CONVOYOR_TEST_H +#define T_IOT_901_CONVOYOR_TEST_H + +void test_construct_dolibarr_client(); + +#endif //T_IOT_901_CONVOYOR_TEST_H