15 Commits

Author SHA1 Message Date
fe529b4f57 reset main to initial state 2024-01-18 15:43:04 +01:00
a924d6c534 add stepper add config 2024-01-18 15:25:33 +01:00
cfffa667b6 fix grbl lib compile 2024-01-18 15:01:41 +01:00
caa5b0ceb7 feat: gestion GRBL STEPPER 2023-11-23 12:55:08 +01:00
0a43f65ce2 gestion initialisation 2023-11-23 12:05:01 +01:00
38415c2da5 change config value 2023-11-23 12:04:37 +01:00
d077deb960 main test GRBL 2023-11-17 11:17:04 +01:00
07528f75f2 add file archi 2023-11-17 11:16:41 +01:00
eb3a6426cd add stepper config 2023-11-17 11:14:26 +01:00
8e8df97e68 add init fonction 2023-11-16 16:07:29 +01:00
7cf9e88995 add test stepper 2023-11-16 15:42:54 +01:00
88077f284e Création du module DolibarrClient avec ses différentes routes & création du module WarehouseGUI pour le LCD M5Stack (#7)
Co-authored-by: Nicolas SANS <nicolas.sansd@gmail.com>
Co-authored-by: Clement <c.boesmier@aptatio.com>
Co-authored-by: Clement <clement@jo85.com>
Reviewed-on: #7
Co-authored-by: Nicolas <nicolas.sansd@gmail.com>
Co-committed-by: Nicolas <nicolas.sansd@gmail.com>
2023-11-10 16:47:39 +01:00
e4f009b63e docs: shematics (#12)
<!--
Bravo pour la PR, quelque note pour que tout se passe au mieux :D

- Lier la PR a une issue si elle existe!
- Assurez-vous que le nom de la PR respecte les règles (voir CONTRIBUTING.md)
- faites des rebase pour valider la PR
- Assurez-vous que le CI/CD est au vert
-->

Co-authored-by: Clement <c.boesmier@aptatio.com>
Reviewed-on: #12
2023-11-10 16:42:00 +01:00
258725e8c9 Feat: add doc for Dolibarr 2023-09-28 14:02:35 +02:00
884a181f04 fix: change board 2023-09-04 14:16:28 +02:00
24 changed files with 587 additions and 90 deletions

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
TIME_ZONE='Europe/Paris'
DOLI_URL='http://0.0.0.0'
DB_NAME="dolibarr"
DB_PASS="password"

3
.gitignore vendored
View File

@ -7,7 +7,8 @@
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.idea/
# Aptatio/Platformio specifics
secrets.ini
.idea
.env

View File

@ -13,7 +13,13 @@ build_flags =
; DO NOT TOUCH --- START
-D MONITOR_SPEED=${config.monitor_speed}
; DO NOT TOUCH --- END
-D WAITING_WIFI_DELAY=1000
-D EXAMPLE_NUMBER=69
-D EXAMPLE_STRING=\"Pouet\"
;;;;;;;;;;;;;;;;;;;;;;
;;; stepper config ;;;
;;;;;;;;;;;;;;;;;;;;;;
-D STEPMOTOR_I2C_ADDR=0x70
-D STEPER_ACC=200
;-D STEPER_PAS=755.906 ; = 65mm
-D STEPER_PAS=58 ; = 5mm
-D STEPER_SPEED=1000 ; 12000

10
config_api.ini Normal file
View File

@ -0,0 +1,10 @@
[config_api]
build_flags =
-D API_LOGIN_URL=\"/users/info\"
-D API_LIST_PRODUCT_URL=\"/products/\"
-D API_GET_PRODUCT_URL=\"/products/{id}/\"
-D API_GET_PRODUCT_STOCK_URL=\"/products/{id}/stock/\"
-D API_LIST_WAREHOUSE_URL=\"/warehouses/\"
-D API_LIST_STOCKS_MOVEMENTS_URL=\"/stockmovements/?sortfield=t.rowid\"
-D API_CREATE_STOCKS_MOVEMENTS_URL=\"/stockmovements/\"
-D API_MAX_JSON_SIZE=4096

View File

@ -1,22 +0,0 @@
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

27
docker-compose.yml Normal file
View File

@ -0,0 +1,27 @@
version: "3"
services:
mariadb:
image: mariadb:latest
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASS}
MYSQL_DATABASE: ${DB_NAME}
volumes:
- database:/var/lib/mysql
restart: always
web:
image: tuxgasy/dolibarr
environment:
DOLI_DB_HOST: mariadb
DOLI_DB_USER: root
DOLI_DB_PASSWORD: ${DB_PASS}
DOLI_DB_NAME: ${DB_NAME}
DOLI_URL_ROOT: ${DOLI_URL}
PHP_INI_DATE_TIMEZONE: ${TIME_ZONE}
restart: always
# ports:
# - "80:80"
depends_on:
- mariadb
volumes:
database:

64
docs/Doc_Dolibarr.md Normal file
View File

@ -0,0 +1,64 @@
# CRM Dolibarr
## Docker Image tuxgasy/dolibarr
### Description du composant
Dolibarr est le CRM utilisé pour gérer les stocks, les transactions et l'inventaire présents dans les entrepôts. Il est le pilier central du projet et nous servira d'interface pour gérer les transactions.
### Spécifications techniques
- **Image Docker**: [tuxgasy/dolibarr](https://hub.docker.com/r/tuxgasy/dolibarr/)
- **Plugins utilisés**: Stock, API REST
- **Utilisateur technique créé**: Technical User IoT
### Fonctionnalités principales
- Créer / Lister les différents entrepôts
- Créer / Lister les produits disponibles dans un entrepôt
- Créer un mouvement dans les stocks des produits disponibles dans chaque entrepôt
- Exposer différents webservices pour automatiser certaines tâches
### Guide d'utilisation
#### Créer une instance de développement
Pour créer une instance de développement, nous allons utiliser l'image Docker de Dolibarr: [tuxgasy/dolibarr](https://hub.docker.com/r/tuxgasy/dolibarr/)
Pour simplifier l'installation, un fichier docker-compose est disponible à la racine du projet: `./docker-compose-dolibarr.yml`
Pour lancer Dolibarr, exécutez la commande suivante :
```bash
$ docker compose -f docker-compose-dolibarr.yml up
```
#### Ensuite, il faut se rendre sur [http://0.0.0.0/](http://0.0.0.0/) puis se connecter avec les identifiants par défaut (admin, admin).
### Appel des différents webservices de Dolibarr
L'URL de base pour toutes nos requêtes HTTP (avec l'image Docker) est : `http://0.0.0.0/api/index.php`
Pour commencer, récupérez l'API token d'un utilisateur pour pouvoir effectuer des requêtes API. Assurez-vous que l'utilisateur dispose des autorisations nécessaires.
Ensuite, utilisez ce token pour chaque route API en l'ajoutant dans les en-têtes : `DOLAPIKEY = {{votre_api_token}}` de votre prochaine requête.
Pour récupérer le warehouse de base et vérifier son existence :
- **Méthode**: GET
- **URL**: `warehouses/{id}`
Pour récupérer les produits disponibles :
- **Méthode**: GET
- **URL**: `products?sortfield=t.ref&sortorder=ASC&limit=10000`
Pour créer un mouvement de stock :
- **Méthode**: POST
- **URL**: `stockmovements?sortfield=t.rowid&sortorder=ASC&limit=100`
- **Body (JSON)**:
```json
{
"product_id": "1", // string, - ID du produit à déplacer
"warehouse_id": "1", // string - ID de l'entrepôt
"qty": 60, // int - quantité à déplacer (1 pour positif ou -1 pour enlever un article)
"movementcode": "S-1", // string - code du mouvement
"movementlabel": "Abc" // string - libellé du mouvement
}

View File

@ -0,0 +1,77 @@
@startuml class
hide empty members
class Dolibarr {
+ String getDestination(String tagID)
+ String createStockMovement(String tagID, String warehouseId)
}
package "Managers" {
abstract AManager {
# ILCDScreen lcd
# IServoMotor servo
# IGRBL grbl
# INFCReader nfc
}
class WarehouseManager
WarehouseManager .|> AManager
}
package "Components" {
package "NFCReader" {
interface INFCReader {
{abstract} char* read()
{abstract} bool hasTag()
}
class RC522
RC522 .|> INFCReader
}
package "LCDScreen" {
interface ILCDScreen {
{abstract} void clearScreen()
{abstract} void draw(int x, int y, int h, int w)
{abstract} void drawRect(int x, int y, int h, int w)
}
class M5LCD
M5LCD .|> ILCDScreen
}
package "GRBL" {
interface IGRBL {
{abstract} drive(int x, int y, int z, int step)
{abstract} step(int s)
}
class M5GRBL
M5GRBL .|> IGRBL
}
package "ServoMotor" {
interface IServoMotor {
{abstract} goLeft()
{abstract} goRight()
{abstract} goMiddle()
}
class ServoMotor
ServoMotor .|> IServoMotor
}
}
class Program {
+ Program()
+ void loop
}
AManager <-- IServoMotor
AManager <-- IGRBL
AManager <-- ILCDScreen
AManager <-- INFCReader
Program <-- WarehouseManager
Program <-- Dolibarr
@enduml

View File

@ -0,0 +1,22 @@
@startuml hard wiring
cloud {
[Dolibarr]
}
package "Convoyeur"{
[M5 Core]
[Lecteur NFC] as nfc
[Servo Moteur] as servo
[GRBL]
[Stepper Moteur] as Stepper
}
[Dolibarr] <-- [M5 Core] : API
[M5 Core] --> servo : IO
[M5 Core] <-- nfc : IC2
[M5 Core] --> [GRBL] : SPI
[GRBL] --> Stepper
@enduml

View File

@ -1,58 +0,0 @@
# Dolbarr documentation
### Créer une instance de dev
Pour créer une instance de développement, nous allons utiliser l'image docker de dolibarr:
https://hub.docker.com/r/tuxgasy/dolibarr/
pour simplifier l'installation, un docker-compose est disponible a la racine du projet `./docker-compose-dolibarr.yml`
pour lancer dolibarr:
```shell
$ docker compose -f docker-compose-dolibarr.yml up
```
ensuite il faut se rendre sur http://0.0.0.0/ puis se login avec les credentials par défault (admin, admin)
### Api flow
Base url: http://0.0.0.0/api/index.php/
Pour commencer, il faut récupérer l'api token d'un utilisateur pour pouvoir faire des requêtes api, assurer vous bien que l'utilisateurs a bien les permissions nécéssaires:
![Api token user](../resources/image.png)
ensuite vous pouvez utiliser ce token pour chaque route API en ajoutant dans les headers:
DOLAPIKEY = {{votre_ap_token}}
Récupérer le warehouse de base et vérifier son existance :
```
Method: GET
Url: warehouses/{id}
```
Récupérer les produits disponibles:
```
Method: GET
Url: products?sortfield=t.ref&sortorder=ASC&limit=10000
```
Pour créer un mouvement de stock:
```
Method: POST
Url: stockmovements?sortfield=t.rowid&sortorder=ASC&limit=100
Body (JSON):
{
"product_id": "1", //string, - id of the product to move
"warehouse_id": "1", //string - id of the warehourse
"qty": 60, //int - quantity to move (1 for positive or -1 to remove one item)
"movementcode": "S-1", //string - code of the mouvement
"movementlabel": "Abc" //string - label of the mouvement
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

View File

@ -2,6 +2,8 @@
#define PROGRAM_H
#include "Arduino.h"
#include "DolibarrClient.h"
#include <M5Stack.h>
class Program {
public:
@ -11,9 +13,11 @@ public:
Program();
/**
* Program main loop
* Program WarehouseGUI loop
*/
void loop();
private:
DolibarrClient *client;
};
#endif

View File

@ -0,0 +1,151 @@
#include "DolibarrClient.h"
#include <WiFi.h>
#include <ArduinoJson.h>
#include <iostream>
DolibarrClient::DolibarrClient(struct DolibarrConfig dolibarr_config) : dolibarr(dolibarr_config) {
#if defined(DEBUG)
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_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(API_LOGIN_URL);
int httpResponseCode = client->GET();
if (httpResponseCode > 0) {
StaticJsonDocument<API_MAX_JSON_SIZE> doc;
DeserializationError error = deserializeJson(doc, client->getString().c_str());
if (error) {
delete client;
return -1;
}
delete client;
return 0;
}
delete client;
return -1;
}
String replace_id(const char *str, const char *id) {
String url(str);
url.replace("{id}", id);
return url;
}
std::vector<models::Product> *DolibarrClient::list_products() const {
HTTPClient *client = this->build_url(API_LIST_PRODUCT_URL);
if (client->GET() == HTTP_CODE_OK) {
}
return nullptr;
}
models::Product *DolibarrClient::get_product_by_id(const char* id_product) const {
HTTPClient *client = this->build_url(replace_id(API_GET_PRODUCT_URL, id_product).c_str());
if (client->GET() == HTTP_CODE_OK) {
StaticJsonDocument<API_MAX_JSON_SIZE> 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<models::Warehouse> *DolibarrClient::list_warehouse() const {
HTTPClient *client = this->build_url(API_LIST_WAREHOUSE_URL);
if (client->GET() == HTTP_CODE_OK) {
StaticJsonDocument<API_MAX_JSON_SIZE> 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<models::Warehouse>();
for (auto obj : doc.as<JsonArray>()) {
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("API_CREATE_STOCKS_MOVEMENTS_URL");
StaticJsonDocument<API_MAX_JSON_SIZE> 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()) == HTTP_CODE_OK) {
delete client;
return 0;
}
return -1;
}
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;
}

View File

@ -0,0 +1,35 @@
#ifndef DOLIBARR_CLIENT_H
#define DOLIBARR_CLIENT_H
#include <WiFi.h>
#include <HTTPClient.h>
#include <vector>
#include "DolibarrModels.h"
struct WifiConfig {
const char* ssid;
const char* password;
};
struct DolibarrConfig {
const char* url;
const char* api_key;
};
class DolibarrClient {
public:
DolibarrClient(DolibarrConfig dolibarr_config);
~DolibarrClient() = default;
int login() const;
std::vector<models::Warehouse> *list_warehouse() const;
std::vector<models::Product> *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 DolibarrConfig dolibarr;
int initialize_http_client();
HTTPClient *build_url(const String& url) const;
};
#endif //DOLIBARR_CLIENT_H

View File

@ -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

22
lib/GRBL/include/GRBL.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef GRBL_H
#define GRBL_H
#include <Arduino.h>
#include "Module_GRBL_13.2.h"
class iGRBL{
public:
virtual void init(int speed, double pas, int accel, String mode = "distance") = 0;
virtual void mouveForward(int mm) = 0;
};
class GRBL : public iGRBL{
public:
GRBL(int grblAddr);
void init(int speed, double pas, int accel, String mode = "distance") override;
void mouveForward(int mm = 5) override;
private:
Module_GRBL* grbl;
};
#endif

34
lib/GRBL/src/GRBL.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "../include/GRBL.h"
GRBL::GRBL(int grblAddr){
this->grbl = new Module_GRBL(grblAddr);
}
void GRBL::init(int speed, double pas, int accel, String mode){
char s[1024];
this->grbl->Init(&Wire);
this->grbl->setMode(mode);
sprintf(s,"$0=%f", pas); // step/mm
this->grbl->sendGcode(s);
Serial.println(s);
sprintf(s,"$4=%d", speed); // speed
this->grbl->sendGcode(s);
Serial.println(s);
sprintf(s,"$8=%d", accel); // acceleration, mm/sec^2
this->grbl->sendGcode(s);
Serial.println(s);
sprintf(s,"$3=%d", 500); // puse/µsec
this->grbl->sendGcode(s);
Serial.println(s);
}
void GRBL::mouveForward(int mm){
char s[1024];
sprintf(s, "G1 X%d", mm);
this->grbl->sendGcode(s);
}

View File

@ -17,13 +17,14 @@ extra_configs =
secrets.ini
config.ini
envs.ini
config_api.ini
; Cache folder
build_cache_dir = ./.pio/cache
[env]
; build Envs
build_flags = ${config.build_flags} ${secrets.build_flags}
build_flags = ${config.build_flags} ${secrets.build_flags} ${config_api.build_flags}
; Add scripts for more functionnalities
; see individual scripts for more informations
@ -31,7 +32,7 @@ extra_scripts = pre:scripts/get_additionnal_envs.py
; Device Settings (make sure to fix versions where possible!)
platform = espressif32@4.2.0
board = esp32dev
board = m5stack-core-esp32
framework = arduino
; Monitoring settings
@ -50,6 +51,10 @@ upload_speed = 921600
; librairies (make sure to fix versions where possible!)
lib_deps =
bblanchon/ArduinoJson@^6.21.3 ; JSON serializer et deserializer
m5stack/M5Stack@^0.4.5 ; M5 Lib
m5stack/M5GFX@^0.1.9 ; M5 Lib pour le LCD
m5stack/Module_GRBL_13.2@^0.0.3 ; M5 Lib pour Stepper (GRBL)
; example:
; erropix/ESP32 AnalogWrite@0.2

View File

@ -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

View File

@ -1,10 +1,29 @@
#include "Program.h"
#include "Arduino.h"
#include "DolibarrClient.h"
int initialize_wifi(WifiConfig wifi) {
WiFiClass::mode(WIFI_STA); //Optional
WiFi.setSleep(false);
WiFi.begin(wifi.ssid, 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;
}
Program::Program() {
// Startup
Serial.begin(MONITOR_SPEED);
struct WifiConfig wifi_c = {WIFI_SSID, WIFI_PASSWORD};
struct DolibarrConfig dolibarr = {DOLIBARR_URL, DOLIBARR_API_TOKEN};
initialize_wifi(wifi_c);
this->client = new DolibarrClient(dolibarr);
}
void Program::loop() {
// Loop
}

View File

@ -1,5 +1,4 @@
#include "Program.h"
Program* program;
void setup() {

10
test/dolibarr.cpp Normal file
View File

@ -0,0 +1,10 @@
#include <unity.h>
#include "DolibarrClient.h"
void test_construct_dolibarr_client() {
return;
}
void test_destroy_basic_state() {
return;
}

36
test/test.cpp Normal file
View File

@ -0,0 +1,36 @@
#include <M5Stack.h>
#include <unity.h>
#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();
}

6
test/test.h Normal file
View File

@ -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