diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ec636a3 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +TIME_ZONE='Europe/Paris' +DOLI_URL='http://0.0.0.0' +DB_NAME="dolibarr" +DB_PASS="password" diff --git a/.gitignore b/.gitignore index 53072ca..5df975e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +.idea/ # Aptatio/Platformio specifics secrets.ini +.env diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..03ba48b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "new": "cpp" + } +} \ No newline at end of file diff --git a/config.ini b/config.ini index 11e4023..6503517 100644 --- a/config.ini +++ b/config.ini @@ -13,7 +13,31 @@ build_flags = ; DO NOT TOUCH --- START -D MONITOR_SPEED=${config.monitor_speed} ; DO NOT TOUCH --- END + -D WAITING_WIFI_DELAY=1000 + -D TIMEZONE=\"Europe/Paris\" + ;;;;;;;;;;;;;;;;;;;;;; + ;;; 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=2700 ;1000 ; 2500 + -D GRBL_UPDATE=165 ;update time in ms + ;;;;;;;;;;;;;;;;;;;;;; + ;;; App config ;;; + ;;;;;;;;;;;;;;;;;;;;;; + -D APP_TITLE=\"Warehouse\" + -D APP_VERSION=\"1.0\" + ; nfc addr + -D NFC_ADDR=0x28 + ;;;;;;;;;;;;;;;;;;;;;; + ;;; Servo config ;;; + ;;;;;;;;;;;;;;;;;;;;;; + -D RIGHT_POS=14 ;18 + -D MIDDLE_POS=30 ;32 + -D LEFT_POS=52 - -D EXAMPLE_NUMBER=69 - -D EXAMPLE_STRING=\"Pouet\" + + -D CONVOYER_LEN=80 ;mm diff --git a/config_api.ini b/config_api.ini new file mode 100644 index 0000000..595c228 --- /dev/null +++ b/config_api.ini @@ -0,0 +1,11 @@ +[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_GET_PRODUCT_FACTORY_ID=\"/products\" + -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=6536 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ee1200a --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/docs/Doc_Dolibarr.md b/docs/Doc_Dolibarr.md new file mode 100644 index 0000000..26d0096 --- /dev/null +++ b/docs/Doc_Dolibarr.md @@ -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 + } diff --git a/docs/Schématics/Class.puml b/docs/Schématics/Class.puml new file mode 100644 index 0000000..2d66a83 --- /dev/null +++ b/docs/Schématics/Class.puml @@ -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 diff --git a/docs/Schématics/Hardware.puml b/docs/Schématics/Hardware.puml new file mode 100644 index 0000000..4c9a86e --- /dev/null +++ b/docs/Schématics/Hardware.puml @@ -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 diff --git a/include/Program.h b/include/Program.h index 66e4a79..ce4f803 100644 --- a/include/Program.h +++ b/include/Program.h @@ -1,7 +1,12 @@ #ifndef PROGRAM_H #define PROGRAM_H -#include "Arduino.h" +#include "DolibarrClient.h" +#include +#include "ServoMotorComponent.h" +#include "NfcReader.h" +#include "GRBL.h" +#include "BigNfcReader.h" class Program { public: @@ -11,9 +16,19 @@ public: Program(); /** - * Program main loop + * Program WarehouseGUI loop */ void loop(); + void checkNfc(); + void checkServo(); + void checkWifi(); +private: + DolibarrClient *client; + ServoMotorComponent *servo; + NfcReader *nfcReader; + GRBL *grbl; + BigNfcReader* outputReader; + int grblUpdateTime; }; #endif diff --git a/lib/DolibarrClient/src/DolibarrClient.cpp b/lib/DolibarrClient/src/DolibarrClient.cpp new file mode 100644 index 0000000..30b3ef2 --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrClient.cpp @@ -0,0 +1,163 @@ +#include "DolibarrClient.h" +#include +#include +#include +#include "M5LCD.h" + +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) { + DynamicJsonDocument doc(384); + DeserializationError error = deserializeJson(doc, client->getString().c_str()); + if (error) { + Serial.println("ERROR: "); + Serial.println(error.c_str()); + 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 *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_factory_id(const char* factory_id) const { + HTTPClient *client = this->build_url(replace_id("/products?sortorder=ASC&limit=1&sqlfilters=(t.accountancy_code_sell:like:'{id}')", factory_id).c_str()); + if (client->GET() == HTTP_CODE_OK) { + DynamicJsonDocument doc(6144); + DeserializationError error = deserializeJson(doc, client->getString().c_str()); + if (error) { + Serial.println("ERROR: "); + Serial.println(error.c_str()); + delete client; + return {}; + } + for (auto obj : doc.as()) { + models::Product product; + product.date_creation = obj["date_creation"].as(); + product.id = obj["id"].as(); + product.entity = obj["entity"].as(); + product.stock_reel = obj["stock_reel"].as(); + product.label = obj["label"].as(); + product.accountancy_code_sell = obj["accountancy_code_sell"].as(); + product.accountancy_code_sell_export = obj["accountancy_code_sell_export"].as(); + product.fk_default_warehouse = obj["fk_default_warehouse"].as(); + delete client; + return product; + } + delete client; + return {}; + } + delete client; + return {}; +} + +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) { + DynamicJsonDocument doc(6144); + DeserializationError error = deserializeJson(doc, client->getString().c_str()); + if (error) { + delete client; + return nullptr; + } + auto *product = new models::Product(); + product->date_creation = doc["date_creation"].as(); + product->id = doc["id"].as(); + product->entity = doc["entity"].as(); + product->stock_reel = doc["stock_reel"].as(); + product->label = doc["label"].as(); + product->accountancy_code_sell = doc["accountancy_code_sell"].as(); + product->accountancy_code_sell_export = doc["accountancy_code_sell_export"].as(); + delete client; + return product; + } + delete client; + return nullptr; +} + +std::vector *DolibarrClient::list_warehouse() const { + HTTPClient *client = this->build_url(API_LIST_WAREHOUSE_URL); + if (client->GET() == HTTP_CODE_OK) { + DynamicJsonDocument doc(8192); + 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"].as(); + warehouse.label = obj["label"].as(); + 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); + DynamicJsonDocument doc(4096); + std::string result; + doc["product_id"] = stock.product_id; + doc["warehouse_id"] = stock.warehouse_id; + doc["qty"] = stock.qty; + doc["movementlabel"] = "T-IOT - Warehouse GUI"; + doc["movementcode"] = "M" + stock.product_id + "-W" + stock.warehouse_id; + 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(); + 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..748d7aa --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrClient.h @@ -0,0 +1,36 @@ +#ifndef DOLIBARR_CLIENT_H +#define DOLIBARR_CLIENT_H + +#include +#include +#include +#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 *list_warehouse() const; + std::vector *list_products() const; + models::Product *get_product_by_id(const char* id_product) const; + models::Product get_product_by_factory_id(const char* uuid) 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 diff --git a/lib/DolibarrClient/src/DolibarrModels.h b/lib/DolibarrClient/src/DolibarrModels.h new file mode 100644 index 0000000..59d0924 --- /dev/null +++ b/lib/DolibarrClient/src/DolibarrModels.h @@ -0,0 +1,44 @@ +#ifndef T_IOT_901_CONVOYOR_DOLIBARRMODELS_H +#define T_IOT_901_CONVOYOR_DOLIBARRMODELS_H + +namespace models { + + struct Product { + std::string id; + std::string entity; + std::string ref; + std::string status; + std::string date_creation; + std::string date_modification; + std::string label; + std::string description; + std::string type; + std::string accountancy_code_sell; + std::string accountancy_code_sell_export; + std::string fk_default_warehouse; + std::string price; + std::string stock_reel; + std::string seuil_stock_alerte; + std::string desiredstock; + }; + + struct ProductStock { + std::string id; + std::string product_id; + std::string quantity; + }; + + struct CreateProductStock { + std::string product_id; + std::string warehouse_id; + std::string qty; + }; + + struct Warehouse { + std::string id; + std::string label; + }; + +} + +#endif //T_IOT_901_CONVOYOR_DOLIBARRMODELS_H diff --git a/lib/GRBL/include/GRBL.h b/lib/GRBL/include/GRBL.h new file mode 100644 index 0000000..acd3ed4 --- /dev/null +++ b/lib/GRBL/include/GRBL.h @@ -0,0 +1,24 @@ +#ifndef GRBL_H +#define GRBL_H + +#include +#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; + virtual bool isIddle() = 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; + bool isIddle() override; +private: + Module_GRBL* grbl; +}; + +#endif diff --git a/lib/GRBL/src/GRBL.cpp b/lib/GRBL/src/GRBL.cpp new file mode 100644 index 0000000..a674180 --- /dev/null +++ b/lib/GRBL/src/GRBL.cpp @@ -0,0 +1,44 @@ +#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); +} + +bool GRBL::isIddle(){ + bool sortie = false; + + if(this->grbl->readStatus().indexOf("IDLE") != -1){ + sortie = true; + } + + return sortie; +} diff --git a/lib/M5LCD/src/M5LCD.cpp b/lib/M5LCD/src/M5LCD.cpp new file mode 100644 index 0000000..59096c3 --- /dev/null +++ b/lib/M5LCD/src/M5LCD.cpp @@ -0,0 +1,224 @@ +#include "M5LCD.h" + +/* + * M5LCD classe + */ + +M5LCD::M5LCD() : _current_page(0), _logs() , _debug_loc_y(0), _components_status({COMPONENT_KO, COMPONENT_KO, COMPONENT_KO, COMPONENT_KO, COMPONENT_KO}) { + this->_product_id = std::string(""); + this->_product_label = std::string(""); + this->_last_nfc = std::string(""); + this->_servo_current_position = std::string(""); + this->_dolibarr_msg = std::string(""); + M5.begin(); + M5.Power.begin(); + M5.lcd.setBrightness(100); + this->update_page(); +} + +void M5LCD::update() { + M5.update(); + if (M5.BtnB.wasReleased() != 0) { + this->_current_page = (this->_current_page + 1) % LCD_PAGES; + this->update_page(); + } + if (this->_current_page == DEBUG_SCREEN && M5.BtnA.wasReleased() != 0 && this->_debug_loc_y < 0) { + this->_debug_loc_y++; + this->update_page(); + } + if (this->_current_page == DEBUG_SCREEN && M5.BtnC.wasReleased() != 0) { + this->_debug_loc_y--; + this->update_page(); + } +} + +void M5LCD::update_page() const { + M5.Lcd.clear(); + switch (this->_current_page) { + case 0: + this->show_dashboard(); + break; + case 1: + this->show_debug(); + break; + case 2: + this->show_config(); + break; + } + this->update_pagination(); +} + +void M5LCD::display_error(const char *str) const { + M5.Lcd.setTextColor(WHITE, RED); + M5.Lcd.setTextSize(1); + M5.Lcd.setCursor(0, 40); + M5.Lcd.println(str); +} + +void M5LCD::display_message(const char *str) const { +} + +void M5LCD::display_warning(const char *str) const { +} + +std::string get_status(AvailableComponentsStatus status) { + return status == COMPONENT_OK ? "OK" : "KO"; +} + +void draw_component_stats(int x, int y, int fx, int fy, int fw, int fh, const char *name, AvailableComponentsStatus status) { + M5.Lcd.setTextSize(2); + if (status == COMPONENT_OK) { + M5.Lcd.setTextColor(WHITE, GREEN); + } else { + M5.Lcd.setTextColor(WHITE, RED); + } + if (status == COMPONENT_OK) { + M5.Lcd.fillRect(fx, fy, fw, fh, GREEN); + } else { + M5.Lcd.fillRect(fx, fy, fw, fh, RED); + } + M5.Lcd.setCursor(x, y); + M5.Lcd.printf("%s %s", name, get_status(status).c_str()); +} + +void M5LCD::show_dashboard() const { + draw_component_stats(10, 18, 0, 0, 100, 50, "NFC", _components_status.nfc); + draw_component_stats(150, 18, 110, 0, 210, 50, "DOLIBARR", _components_status.dolibarr); + draw_component_stats(8, 78, 0, 60, 100, 50, "WIFI", _components_status.wifi); + draw_component_stats(112, 78, 110, 60, 100, 50, "SERVO", _components_status.servo); + draw_component_stats(230, 78, 220, 60, 100, 50, "GRBL", _components_status.grbl); + + M5.Lcd.drawRect(0, 120, 320, 100, BLUE); + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setTextSize(2); + if (!this->_last_nfc.empty()) { + M5.Lcd.setCursor(10, 130); + M5.Lcd.printf("NFC: %s", this->_last_nfc.c_str()); + } + if (!this->_product_id.empty() && !this->_product_label.empty()) { + M5.Lcd.setCursor(10, 150); + M5.Lcd.printf("Produit: %s - %s", this->_product_label.c_str(), this->_product_id.c_str()); + } + if (!this->_dolibarr_msg.empty()) { + M5.Lcd.setCursor(10, 170); + M5.Lcd.printf("Produit: %s", this->_last_nfc.c_str()); + } + if (!this->_servo_current_position.empty()) { + M5.Lcd.setCursor(10, 190); + M5.Lcd.printf("SERVO Posistion: %s", this->_servo_current_position.c_str()); + } + + M5.Lcd.setTextSize(1); + M5.Lcd.setCursor(0, 230); + M5.Lcd.printf("%s - %s", APP_TITLE, APP_VERSION); +} + +void M5LCD::show_debug() const { + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setTextSize(1); + int i = 0; + for (auto val : this->_logs) { + M5.Lcd.setCursor(0, i + (this->_debug_loc_y * 10)); + M5.Lcd.printf("[%s] - %s", val.get_datetime_format(), val.get_message().c_str()); + i+=10; + } +} + +void M5LCD::update_pagination() const { + M5.Lcd.setTextSize(1); + M5.Lcd.setTextColor(WHITE, BLACK); + M5.Lcd.setCursor(302, 230); + M5.Lcd.printf("%d/%d", this->_current_page+1, LCD_PAGES); +} + +void M5LCD::add_log(const char* str) { + this->_logs.emplace_back(str); + if (this->_current_page == DEBUG_SCREEN) { + this->show_debug(); + } +} + +void M5LCD::show_config() const { + +} + +ComponentsStatus M5LCD::get_components() { + return this->_components_status; +} + +void M5LCD::set_wifi_status(AvailableComponentsStatus status) { + this->_components_status.wifi = status; + this->update_dashboard(); +} + +void M5LCD::set_nfc_status(AvailableComponentsStatus status) { + this->_components_status.nfc = status; + this->update_dashboard(); +} + +void M5LCD::set_grbl_status(AvailableComponentsStatus status) { + this->_components_status.grbl = status; + this->update_dashboard(); +} + +void M5LCD::set_servo(AvailableComponentsStatus status) { + this->_components_status.servo = status; + this->update_dashboard(); +} + +void M5LCD::set_dolibarr_status(AvailableComponentsStatus status) { + this->_components_status.dolibarr = status; + this->update_dashboard(); +} + +void M5LCD::update_dashboard() const { + if (this->_current_page == DASHBOARD_SCREEN) { + this->show_dashboard(); + } +} + + +void M5LCD::set_dolibarr_message(std::string str) { + this->_dolibarr_msg = str; + this->update_dashboard(); +} + +void M5LCD::set_nfc_message(std::string str) { + this->_last_nfc = str; + this->update_dashboard(); +} + +void M5LCD::set_product_id(std::string str) { + this->_product_id = str; + this->update_dashboard(); +} + +void M5LCD::set_servo_message(std::string str) { + this->_servo_current_position = str; + this->update_dashboard(); +} + +void M5LCD::set_product_label(std::string str) { + this->_product_label = str; + this->update_dashboard(); + +} + +/* + * LogMessage classe + */ + +LogMessage::LogMessage(std::string log) { + this->log = log; + this->datetime = time(nullptr); +} + +std::string LogMessage::get_message() const { + return this->log; +} + +const char *LogMessage::get_datetime_format() const { + void *buff = malloc(20 * sizeof(char)); + strftime((char*) buff, 20, "%H:%M:%S", localtime(&this->datetime)); + return (char*) buff; +} diff --git a/lib/M5LCD/src/M5LCD.h b/lib/M5LCD/src/M5LCD.h new file mode 100644 index 0000000..00de43c --- /dev/null +++ b/lib/M5LCD/src/M5LCD.h @@ -0,0 +1,84 @@ +#ifndef T_IOT_901_CONVOYOR_M5LCD_H +#define T_IOT_901_CONVOYOR_M5LCD_H + +#include +#include +#include "M5Stack.h" + +#define LCD_PAGES 3 +#define DASHBOARD_SCREEN 0 +#define DEBUG_SCREEN 1 +#define CONFIG_SCREEN 2 + +enum AvailableComponentsStatus { + COMPONENT_OK, + COMPONENT_KO, + COMPONENT_MISSING, + UNKNOWN_ERROR, +}; + +typedef struct ComponentsStatus { + AvailableComponentsStatus nfc; + AvailableComponentsStatus wifi; + AvailableComponentsStatus dolibarr; + AvailableComponentsStatus servo; + AvailableComponentsStatus grbl; +} ComponentsStatus; + +typedef struct MessageToShow { + const char* message; + int color; +} MessageToShow; + +class LogMessage { +public: + LogMessage(std::string log); + std::string get_message() const; + const char* get_datetime_format() const; +private: + time_t datetime; + std::string log; +}; + +class M5LCD { +public: + M5LCD(); + void display_message(const char* str) const; + void display_error(const char* str) const; + void display_warning(const char* str) const; + void add_log(const char *str); + void update(); + ComponentsStatus get_components(); + void set_wifi_status(AvailableComponentsStatus status); + void set_nfc_status(AvailableComponentsStatus status); + void set_grbl_status(AvailableComponentsStatus status); + void set_servo(AvailableComponentsStatus status); + void set_dolibarr_status(AvailableComponentsStatus status); + + void set_nfc_message(std::string str); + void set_dolibarr_message(std::string str); + void set_product_label(std::string str); + void set_product_id(std::string str); + void set_servo_message(std::string str); +private: + void update_page() const; + void show_debug() const; + void show_dashboard() const; + void show_config() const; + void update_pagination() const; + void update_dashboard() const; + + int _current_page; + int _debug_loc_y; + std::vector _logs; + ComponentsStatus _components_status; + std::string _last_nfc; + std::string _product_label; + std::string _product_id; + std::string _dolibarr_msg; + std::string _servo_current_position; +}; + +inline M5LCD *lcdScreen; + +#endif //T_IOT_901_CONVOYOR_M5LCD_H diff --git a/lib/NFC/src/MFRC522_I2C.cpp b/lib/NFC/src/MFRC522_I2C.cpp new file mode 100644 index 0000000..369299b --- /dev/null +++ b/lib/NFC/src/MFRC522_I2C.cpp @@ -0,0 +1,2059 @@ +/* + * MFRC522.cpp - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS I2C + * BY AROZCAN MFRC522.cpp - Based on ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS + * SPI Library BY COOQROBOT. NOTE: Please also check the comments in MFRC522.h - + * they provide useful hints and background information. Released into the + * public domain. Author: arozcan @ + * https://github.com/arozcan/MFRC522-I2C-Library + */ + +#include "MFRC522_I2C.h" + +#include +#include + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for setting up the Arduino +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Constructor. + * Prepares the output pins. + */ +MFRC522::MFRC522( + byte chipAddress + // byte resetPowerDownPin ///< Arduino pin connected to MFRC522's reset + // and power down input (Pin 6, NRSTPD, active low) +) { + _chipAddress = chipAddress; + // _resetPowerDownPin = resetPowerDownPin; +} // End constructor + +///////////////////////////////////////////////////////////////////////////////////// +// Basic interface functions for communicating with the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Writes a byte to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void MFRC522::PCD_WriteRegister( + byte reg, ///< The register to write to. One of the PCD_Register enums. + byte value ///< The value to write. +) { + Wire.beginTransmission(_chipAddress); + Wire.write(reg); + Wire.write(value); + Wire.endTransmission(); +} // End PCD_WriteRegister() + +/** + * Writes a number of bytes to the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void MFRC522::PCD_WriteRegister( + byte reg, ///< The register to write to. One of the PCD_Register enums. + byte count, ///< The number of bytes to write to the register + byte *values ///< The values to write. Byte array. +) { + Wire.beginTransmission(_chipAddress); + Wire.write(reg); + for (byte index = 0; index < count; index++) { + Wire.write(values[index]); + } + Wire.endTransmission(); +} // End PCD_WriteRegister() + +/** + * Reads a byte from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +byte MFRC522::PCD_ReadRegister( + byte reg ///< The register to read from. One of the PCD_Register enums. +) { + byte value; + // digitalWrite(_chipSelectPin, LOW); // Select slave + Wire.beginTransmission(_chipAddress); + Wire.write(reg); + Wire.endTransmission(); + const uint8_t sz = 1; + Wire.requestFrom(_chipAddress, sz); + value = Wire.read(); + return value; +} // End PCD_ReadRegister() + +/** + * Reads a number of bytes from the specified register in the MFRC522 chip. + * The interface is described in the datasheet section 8.1.2. + */ +void MFRC522::PCD_ReadRegister( + byte reg, ///< The register to read from. One of the PCD_Register enums. + byte count, ///< The number of bytes to read + byte *values, ///< Byte array to store the values in. + byte rxAlign ///< Only bit positions rxAlign..7 in values[0] are updated. +) { + if (count == 0) { + return; + } + byte address = reg; + byte index = 0; // Index in values array. + Wire.beginTransmission(_chipAddress); + Wire.write(address); + Wire.endTransmission(); + Wire.requestFrom(_chipAddress, count); + while (Wire.available()) { + if (index == 0 && + rxAlign) { // Only update bit positions rxAlign..7 in values[0] + // Create bit mask for bit positions rxAlign..7 + byte mask = 0; + for (byte i = rxAlign; i <= 7; i++) { + mask |= (1 << i); + } + // Read value and tell that we want to read the same address again. + byte value = Wire.read(); + // Apply mask to both current value of values[0] and the new data in + // value. + values[0] = (values[index] & ~mask) | (value & mask); + } else { // Normal case + values[index] = Wire.read(); + } + index++; + } +} // End PCD_ReadRegister() + +/** + * Sets the bits given in mask in register reg. + */ +void MFRC522::PCD_SetRegisterBitMask( + byte reg, ///< The register to update. One of the PCD_Register enums. + byte mask ///< The bits to set. +) { + byte tmp; + tmp = PCD_ReadRegister(reg); + PCD_WriteRegister(reg, tmp | mask); // set bit mask +} // End PCD_SetRegisterBitMask() + +/** + * Clears the bits given in mask from register reg. + */ +void MFRC522::PCD_ClearRegisterBitMask( + byte reg, ///< The register to update. One of the PCD_Register enums. + byte mask ///< The bits to clear. +) { + byte tmp; + tmp = PCD_ReadRegister(reg); + PCD_WriteRegister(reg, tmp & (~mask)); // clear bit mask +} // End PCD_ClearRegisterBitMask() + +/** + * Use the CRC coprocessor in the MFRC522 to calculate a CRC_A. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PCD_CalculateCRC( + byte *data, ///< In: Pointer to the data to transfer to the FIFO for CRC + ///< calculation. + byte length, ///< In: The number of bytes to transfer. + byte *result ///< Out: Pointer to result buffer. Result is written to + ///< result[0..1], low byte first. +) { + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. + PCD_WriteRegister(DivIrqReg, + 0x04); // Clear the CRCIRq interrupt request bit + PCD_SetRegisterBitMask(FIFOLevelReg, + 0x80); // FlushBuffer = 1, FIFO initialization + PCD_WriteRegister(FIFODataReg, length, data); // Write data to the FIFO + PCD_WriteRegister(CommandReg, PCD_CalcCRC); // Start the calculation + + // Wait for the CRC calculation to complete. Each iteration of the + // while-loop takes 17.73�s. + word i = 5000; + byte n; + while (1) { + n = PCD_ReadRegister( + DivIrqReg); // DivIrqReg[7..0] bits are: Set2 reserved reserved + // MfinActIRq reserved CRCIRq reserved reserved + if (n & 0x04) { // CRCIRq bit set - calculation done + break; + } + if (--i == 0) { // The emergency break. We will eventually terminate on + // this one after 89ms. Communication with the MFRC522 + // might be down. + return STATUS_TIMEOUT; + } + } + PCD_WriteRegister( + CommandReg, + PCD_Idle); // Stop calculating CRC for new content in the FIFO. + + // Transfer the result from the registers to the result buffer + result[0] = PCD_ReadRegister(CRCResultRegL); + result[1] = PCD_ReadRegister(CRCResultRegH); + return STATUS_OK; +} // End PCD_CalculateCRC() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for manipulating the MFRC522 +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the MFRC522 chip. + */ +void MFRC522::PCD_Init() { + // Set the chipSelectPin as digital output, do not select the slave yet + + // Set the resetPowerDownPin as digital output, do not reset or power down. + // pinMode(_resetPowerDownPin, OUTPUT); + + // if (digitalRead(_resetPowerDownPin) == LOW) { //The MFRC522 chip is in + // power down mode. digitalWrite(_resetPowerDownPin, HIGH); // Exit + // power down mode. This triggers a hard reset. + // // Section 8.8.2 in the datasheet says the oscillator start-up time is + // the start up time of the crystal + 37,74�s. Let us be generous: 50ms. + // delay(50); + // } + // else { // Perform a soft reset + PCD_Reset(); + // } + + // When communicating with a PICC we need a timeout if something goes wrong. + // f_timer = 13.56 MHz / (2*TPreScaler+1) where TPreScaler = + // [TPrescaler_Hi:TPrescaler_Lo]. TPrescaler_Hi are the four low bits in + // TModeReg. TPrescaler_Lo is TPrescalerReg. + PCD_WriteRegister( + TModeReg, + 0x80); // TAuto=1; timer starts automatically at the end of the + // transmission in all communication modes at all speeds + PCD_WriteRegister( + TPrescalerReg, + 0xA9); // TPreScaler = TModeReg[3..0]:TPrescalerReg, ie 0x0A9 = 169 => + // f_timer=40kHz, ie a timer period of 25�s. + PCD_WriteRegister( + TReloadRegH, + 0x03); // Reload timer with 0x3E8 = 1000, ie 25ms before timeout. + PCD_WriteRegister(TReloadRegL, 0xE8); + + PCD_WriteRegister(TxASKReg, + 0x40); // Default 0x00. Force a 100 % ASK modulation + // independent of the ModGsPReg register setting + PCD_WriteRegister( + ModeReg, + 0x3D); // Default 0x3F. Set the preset value for the CRC coprocessor + // for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4) + PCD_AntennaOn(); // Enable the antenna driver pins TX1 and TX2 (they were + // disabled by the reset) +} // End PCD_Init() + +/** + * Performs a soft reset on the MFRC522 chip and waits for it to be ready again. + */ +void MFRC522::PCD_Reset() { + PCD_WriteRegister(CommandReg, + PCD_SoftReset); // Issue the SoftReset command. + // The datasheet does not mention how long the SoftRest command takes to + // complete. But the MFRC522 might have been in soft power-down mode + // (triggered by bit 4 of CommandReg) Section 8.8.2 in the datasheet says + // the oscillator start-up time is the start up time of the crystal + + // 37,74�s. Let us be generous: 50ms. + delay(50); + // Wait for the PowerDown bit in CommandReg to be cleared + while (PCD_ReadRegister(CommandReg) & (1 << 4)) { + // PCD still restarting - unlikely after waiting 50ms, but better safe + // than sorry. + } +} // End PCD_Reset() + +/** + * Turns the antenna on by enabling pins TX1 and TX2. + * After a reset these pins are disabled. + */ +void MFRC522::PCD_AntennaOn() { + byte value = PCD_ReadRegister(TxControlReg); + if ((value & 0x03) != 0x03) { + PCD_WriteRegister(TxControlReg, value | 0x03); + } +} // End PCD_AntennaOn() + +/** + * Turns the antenna off by disabling pins TX1 and TX2. + */ +void MFRC522::PCD_AntennaOff() { + PCD_ClearRegisterBitMask(TxControlReg, 0x03); +} // End PCD_AntennaOff() + +/** + * Get the current MFRC522 Receiver Gain (RxGain[2:0]) value. + * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * NOTE: Return value scrubbed with (0x07<<4)=01110000b as RCFfgReg may use + * reserved bits. + * + * @return Value of the RxGain, scrubbed to the 3 bits used. + */ +byte MFRC522::PCD_GetAntennaGain() { + return PCD_ReadRegister(RFCfgReg) & (0x07 << 4); +} // End PCD_GetAntennaGain() + +/** + * Set the MFRC522 Receiver Gain (RxGain) to value specified by given mask. + * See 9.3.3.6 / table 98 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * NOTE: Given mask is scrubbed with (0x07<<4)=01110000b as RCFfgReg may use + * reserved bits. + */ +void MFRC522::PCD_SetAntennaGain(byte mask) { + if (PCD_GetAntennaGain() != mask) { // only bother if there is a change + PCD_ClearRegisterBitMask( + RFCfgReg, (0x07 << 4)); // clear needed to allow 000 pattern + PCD_SetRegisterBitMask( + RFCfgReg, mask & (0x07 << 4)); // only set RxGain[2:0] bits + } +} // End PCD_SetAntennaGain() + +/** + * Performs a self-test of the MFRC522 + * See 16.1.1 in http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * @return Whether or not the test passed. + */ +bool MFRC522::PCD_PerformSelfTest() { + // This follows directly the steps outlined in 16.1.1 + // 1. Perform a soft reset. + PCD_Reset(); + + // 2. Clear the internal buffer by writing 25 bytes of 00h + byte ZEROES[25] = {0x00}; + PCD_SetRegisterBitMask(FIFOLevelReg, 0x80); // flush the FIFO buffer + PCD_WriteRegister(FIFODataReg, 25, + ZEROES); // write 25 bytes of 00h to FIFO + PCD_WriteRegister(CommandReg, PCD_Mem); // transfer to internal buffer + + // 3. Enable self-test + PCD_WriteRegister(AutoTestReg, 0x09); + + // 4. Write 00h to FIFO buffer + PCD_WriteRegister(FIFODataReg, 0x00); + + // 5. Start self-test by issuing the CalcCRC command + PCD_WriteRegister(CommandReg, PCD_CalcCRC); + + // 6. Wait for self-test to complete + word i; + byte n; + for (i = 0; i < 0xFF; i++) { + n = PCD_ReadRegister( + DivIrqReg); // DivIrqReg[7..0] bits are: Set2 reserved reserved + // MfinActIRq reserved CRCIRq reserved reserved + if (n & 0x04) { // CRCIRq bit set - calculation done + break; + } + } + PCD_WriteRegister( + CommandReg, + PCD_Idle); // Stop calculating CRC for new content in the FIFO. + + // 7. Read out resulting 64 bytes from the FIFO buffer. + byte result[64]; + PCD_ReadRegister(FIFODataReg, 64, result, 0); + + // Auto self-test done + // Reset AutoTestReg register to be 0 again. Required for normal operation. + PCD_WriteRegister(AutoTestReg, 0x00); + + // Determine firmware version (see section 9.3.4.8 in spec) + byte version = PCD_ReadRegister(VersionReg); + + // Pick the appropriate reference values + const byte *reference; + switch (version) { + case 0x88: // Fudan Semiconductor FM17522 clone + reference = FM17522_firmware_reference; + break; + case 0x90: // Version 0.0 + reference = MFRC522_firmware_referenceV0_0; + break; + case 0x91: // Version 1.0 + reference = MFRC522_firmware_referenceV1_0; + break; + case 0x92: // Version 2.0 + reference = MFRC522_firmware_referenceV2_0; + break; + default: // Unknown version + return false; + } + + // Verify that the results match up to our expectations + for (i = 0; i < 64; i++) { + if (result[i] != pgm_read_byte(&(reference[i]))) { + return false; + } + } + + // Test passed; all is good. + return true; +} // End PCD_PerformSelfTest() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the Transceive command. + * CRC validation can only be done if backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PCD_TransceiveData( + byte *sendData, ///< Pointer to the data to transfer to the FIFO. + byte sendLen, ///< Number of bytes to transfer to the FIFO. + byte *backData, ///< NULL or pointer to buffer if data should be read back + ///< after executing the command. + byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The + ///< number of bytes returned. + byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 + ///< for 8 valid bits. Default NULL. + byte rxAlign, ///< In: Defines the bit position in backData[0] for the + ///< first bit received. Default 0. + bool checkCRC ///< In: True => The last two bytes of the response is + ///< assumed to be a CRC_A that must be validated. +) { + byte waitIRq = 0x30; // RxIRq and IdleIRq + return PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, sendData, sendLen, + backData, backLen, validBits, rxAlign, + checkCRC); +} // End PCD_TransceiveData() + +/** + * Transfers data to the MFRC522 FIFO, executes a command, waits for completion + * and transfers data back from the FIFO. CRC validation can only be done if + * backData and backLen are specified. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PCD_CommunicateWithPICC( + byte command, ///< The command to execute. One of the PCD_Command enums. + byte waitIRq, ///< The bits in the ComIrqReg register that signals + ///< successful completion of the command. + byte *sendData, ///< Pointer to the data to transfer to the FIFO. + byte sendLen, ///< Number of bytes to transfer to the FIFO. + byte *backData, ///< NULL or pointer to buffer if data should be read back + ///< after executing the command. + byte *backLen, ///< In: Max number of bytes to write to *backData. Out: The + ///< number of bytes returned. + byte *validBits, ///< In/Out: The number of valid bits in the last byte. 0 + ///< for 8 valid bits. + byte rxAlign, ///< In: Defines the bit position in backData[0] for the + ///< first bit received. Default 0. + bool checkCRC ///< In: True => The last two bytes of the response is + ///< assumed to be a CRC_A that must be validated. +) { + byte n, _validBits; + unsigned int i; + + // Prepare values for BitFramingReg + byte txLastBits = validBits ? *validBits : 0; + byte bitFraming = + (rxAlign << 4) + txLastBits; // RxAlign = BitFramingReg[6..4]. + // TxLastBits = BitFramingReg[2..0] + + PCD_WriteRegister(CommandReg, PCD_Idle); // Stop any active command. + PCD_WriteRegister(ComIrqReg, + 0x7F); // Clear all seven interrupt request bits + PCD_SetRegisterBitMask(FIFOLevelReg, + 0x80); // FlushBuffer = 1, FIFO initialization + PCD_WriteRegister(FIFODataReg, sendLen, + sendData); // Write sendData to the FIFO + PCD_WriteRegister(BitFramingReg, bitFraming); // Bit adjustments + PCD_WriteRegister(CommandReg, command); // Execute the command + if (command == PCD_Transceive) { + PCD_SetRegisterBitMask( + BitFramingReg, 0x80); // StartSend=1, transmission of data starts + } + + // Wait for the command to complete. + // In PCD_Init() we set the TAuto flag in TModeReg. This means the timer + // automatically starts when the PCD stops transmitting. Each iteration of + // the do-while-loop takes 17.86�s. + i = 2000; + while (1) { + n = PCD_ReadRegister( + ComIrqReg); // ComIrqReg[7..0] bits are: Set1 TxIRq RxIRq IdleIRq + // HiAlertIRq LoAlertIRq ErrIRq TimerIRq + if (n & waitIRq) { // One of the interrupts that signal success has + // been set. + break; + } + if (n & 0x01) { // Timer interrupt - nothing received in 25ms + return STATUS_TIMEOUT; + } + if (--i == 0) { // The emergency break. If all other condions fail we + // will eventually terminate on this one after 35.7ms. + // Communication with the MFRC522 might be down. + return STATUS_TIMEOUT; + } + } + + // Stop now if any errors except collisions were detected. + byte errorRegValue = PCD_ReadRegister( + ErrorReg); // ErrorReg[7..0] bits are: WrErr TempErr reserved + // BufferOvfl CollErr CRCErr ParityErr ProtocolErr + if (errorRegValue & 0x13) { // BufferOvfl ParityErr ProtocolErr + return STATUS_ERROR; + } + + // If the caller wants data back, get it from the MFRC522. + if (backData && backLen) { + n = PCD_ReadRegister(FIFOLevelReg); // Number of bytes in the FIFO + if (n > *backLen) { + return STATUS_NO_ROOM; + } + *backLen = n; // Number of bytes returned + PCD_ReadRegister(FIFODataReg, n, backData, + rxAlign); // Get received data from FIFO + _validBits = PCD_ReadRegister(ControlReg) & + 0x07; // RxLastBits[2:0] indicates the number of valid + // bits in the last received byte. If this value is + // 000b, the whole byte is valid. + if (validBits) { + *validBits = _validBits; + } + } + + // Tell about collisions + if (errorRegValue & 0x08) { // CollErr + return STATUS_COLLISION; + } + + // Perform CRC_A validation if requested. + if (backData && backLen && checkCRC) { + // In this case a MIFARE Classic NAK is not OK. + if (*backLen == 1 && _validBits == 4) { + return STATUS_MIFARE_NACK; + } + // We need at least the CRC_A value and all 8 bits of the last byte must + // be received. + if (*backLen < 2 || _validBits != 0) { + return STATUS_CRC_WRONG; + } + // Verify CRC_A - do our own calculation and store the control in + // controlBuffer. + byte controlBuffer[2]; + n = PCD_CalculateCRC(&backData[0], *backLen - 2, &controlBuffer[0]); + if (n != STATUS_OK) { + return n; + } + if ((backData[*backLen - 2] != controlBuffer[0]) || + (backData[*backLen - 1] != controlBuffer[1])) { + return STATUS_CRC_WRONG; + } + } + + return STATUS_OK; +} // End PCD_CommunicateWithPICC() + +/** + * Transmits a REQuest command, Type A. Invites PICCs in state IDLE to go to + * READY and prepare for anticollision or selection. 7 bit frame. Beware: When + * two PICCs are in the field at the same time I often get STATUS_TIMEOUT - + * probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PICC_RequestA( + byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in + byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes + ///< returned if STATUS_OK. +) { + return PICC_REQA_or_WUPA(PICC_CMD_REQA, bufferATQA, bufferSize); +} // End PICC_RequestA() + +/** + * Transmits a Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to + * go to READY(*) and prepare for anticollision or selection. 7 bit frame. + * Beware: When two PICCs are in the field at the same time I often get + * STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PICC_WakeupA( + byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in + byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes + ///< returned if STATUS_OK. +) { + return PICC_REQA_or_WUPA(PICC_CMD_WUPA, bufferATQA, bufferSize); +} // End PICC_WakeupA() + +/** + * Transmits REQA or WUPA commands. + * Beware: When two PICCs are in the field at the same time I often get + * STATUS_TIMEOUT - probably due do bad antenna design. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PICC_REQA_or_WUPA( + byte command, ///< The command to send - PICC_CMD_REQA or PICC_CMD_WUPA + byte *bufferATQA, ///< The buffer to store the ATQA (Answer to request) in + byte *bufferSize ///< Buffer size, at least two bytes. Also number of bytes + ///< returned if STATUS_OK. +) { + byte validBits; + byte status; + + if (bufferATQA == NULL || + *bufferSize < 2) { // The ATQA response is 2 bytes long. + return STATUS_NO_ROOM; + } + PCD_ClearRegisterBitMask(CollReg, + 0x80); // ValuesAfterColl=1 => Bits received after + // collision are cleared. + validBits = 7; // For REQA and WUPA we need the short frame format - + // transmit only 7 bits of the last (and only) byte. + // TxLastBits = BitFramingReg[2..0] + status = + PCD_TransceiveData(&command, 1, bufferATQA, bufferSize, &validBits); + if (status != STATUS_OK) { + return status; + } + if (*bufferSize != 2 || validBits != 0) { // ATQA must be exactly 16 bits. + return STATUS_ERROR; + } + return STATUS_OK; +} // End PICC_REQA_or_WUPA() + +/** + * Transmits SELECT/ANTICOLLISION commands to select a single PICC. + * Before calling this function the PICCs must be placed in the READY(*) state + * by calling PICC_RequestA() or PICC_WakeupA(). On success: + * - The chosen PICC is in state ACTIVE(*) and all other PICCs have + * returned to state IDLE/HALT. (Figure 7 of the ISO/IEC 14443-3 draft.) + * - The UID size and value of the chosen PICC is returned in *uid along + * with the SAK. + * + * A PICC UID consists of 4, 7 or 10 bytes. + * Only 4 bytes can be specified in a SELECT command, so for the longer UIDs two + * or three iterations are used: UID size Number of UID bytes Cascade + * levels Example of PICC + * ======== =================== ============== =============== + * single 4 1 MIFARE + * Classic double 7 2 MIFARE + * Ultralight + * triple 10 3 Not + * currently in use? + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PICC_Select( + Uid *uid, ///< Pointer to Uid struct. Normally output, but can also be used + ///< to supply a known UID. + byte validBits ///< The number of known UID bits supplied in *uid. Normally + ///< 0. If set you must also supply uid->size. +) { + bool uidComplete; + bool selectDone; + bool useCascadeTag; + byte cascadeLevel = 1; + byte result; + byte count; + byte index; + byte uidIndex; // The first index in uid->uidByte[] that is used in the + // current Cascade Level. + int8_t currentLevelKnownBits; // The number of known UID bits in the + // current Cascade Level. + byte buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard + // frame + 2 bytes CRC_A + byte bufferUsed; // The number of bytes used in the buffer, ie the number + // of bytes to transfer to the FIFO. + byte rxAlign; // Used in BitFramingReg. Defines the bit position for the + // first bit received. + byte txLastBits; // Used in BitFramingReg. The number of valid bits in the + // last transmitted byte. + byte *responseBuffer; + byte responseLength; + + // Description of buffer structure: + // Byte 0: SEL Indicates the Cascade Level: + // PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3 Byte 1: NVB + // Number of Valid Bits (in complete command, not just the UID): High + // nibble: + // complete bytes, Low nibble: Extra bits. Byte 2: UID-data or CT + // See explanation below. CT means Cascade Tag. Byte 3: UID-data + // Byte 4: UID-data Byte 5: UID-data + // Byte 6: BCC Block Check Character - XOR of bytes 2-5 + // Byte 7: CRC_A + // Byte 8: CRC_A + // The BCC and CRC_A is only transmitted if we know all the UID bits of the + // current Cascade Level. + // + // Description of bytes 2-5: (Section 6.5.4 of the ISO/IEC 14443-3 draft: + // UID contents and cascade levels) + // UID size Cascade level Byte2 Byte3 Byte4 Byte5 + // ======== ============= ===== ===== ===== ===== + // 4 bytes 1 uid0 uid1 uid2 uid3 + // 7 bytes 1 CT uid0 uid1 uid2 + // 2 uid3 uid4 uid5 uid6 + // 10 bytes 1 CT uid0 uid1 uid2 + // 2 CT uid3 uid4 uid5 + // 3 uid6 uid7 uid8 uid9 + + // Sanity checks + if (validBits > 80) { + return STATUS_INVALID; + } + + // Prepare MFRC522 + PCD_ClearRegisterBitMask(CollReg, + 0x80); // ValuesAfterColl=1 => Bits received after + // collision are cleared. + + // Repeat Cascade Level loop until we have a complete UID. + uidComplete = false; + while (!uidComplete) { + // Set the Cascade Level in the SEL byte, find out if we need to use the + // Cascade Tag in byte 2. + switch (cascadeLevel) { + case 1: + buffer[0] = PICC_CMD_SEL_CL1; + uidIndex = 0; + useCascadeTag = + validBits && + uid->size > + 4; // When we know that the UID has more than 4 bytes + break; + + case 2: + buffer[0] = PICC_CMD_SEL_CL2; + uidIndex = 3; + useCascadeTag = + validBits && + uid->size > + 7; // When we know that the UID has more than 7 bytes + break; + + case 3: + buffer[0] = PICC_CMD_SEL_CL3; + uidIndex = 6; + useCascadeTag = false; // Never used in CL3. + break; + + default: + return STATUS_INTERNAL_ERROR; + break; + } + + // How many UID bits are known in this Cascade Level? + currentLevelKnownBits = validBits - (8 * uidIndex); + if (currentLevelKnownBits < 0) { + currentLevelKnownBits = 0; + } + // Copy the known bits from uid->uidByte[] to buffer[] + index = 2; // destination index in buffer[] + if (useCascadeTag) { + buffer[index++] = PICC_CMD_CT; + } + byte bytesToCopy = + currentLevelKnownBits / 8 + + (currentLevelKnownBits % 8 + ? 1 + : 0); // The number of bytes needed to represent the known + // bits for this level. + if (bytesToCopy) { + byte maxBytes = + useCascadeTag ? 3 : 4; // Max 4 bytes in each Cascade Level. + // Only 3 left if we use the Cascade Tag + if (bytesToCopy > maxBytes) { + bytesToCopy = maxBytes; + } + for (count = 0; count < bytesToCopy; count++) { + buffer[index++] = uid->uidByte[uidIndex + count]; + } + } + // Now that the data has been copied we need to include the 8 bits in CT + // in currentLevelKnownBits + if (useCascadeTag) { + currentLevelKnownBits += 8; + } + + // Repeat anti collision loop until we can transmit all UID bits + BCC + // and receive a SAK - max 32 iterations. + selectDone = false; + while (!selectDone) { + // Find out how many bits and bytes to send and receive. + if (currentLevelKnownBits >= + 32) { // All UID bits in this Cascade Level are known. This is + // a SELECT. + // Serial.print(F("SELECT: currentLevelKnownBits=")); + // Serial.println(currentLevelKnownBits, DEC); + buffer[1] = + 0x70; // NVB - Number of Valid Bits: Seven whole bytes + // Calculate BCC - Block Check Character + buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 7, &buffer[7]); + if (result != STATUS_OK) { + return result; + } + txLastBits = 0; // 0 => All 8 bits are valid. + bufferUsed = 9; + // Store response in the last 3 bytes of buffer (BCC and CRC_A - + // not needed after tx) + responseBuffer = &buffer[6]; + responseLength = 3; + } else { // This is an ANTICOLLISION. + // Serial.print(F("ANTICOLLISION: currentLevelKnownBits=")); + // Serial.println(currentLevelKnownBits, DEC); + txLastBits = currentLevelKnownBits % 8; + count = currentLevelKnownBits / + 8; // Number of whole bytes in the UID part. + index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs + buffer[1] = + (index << 4) + txLastBits; // NVB - Number of Valid Bits + bufferUsed = index + (txLastBits ? 1 : 0); + // Store response in the unused part of buffer + responseBuffer = &buffer[index]; + responseLength = sizeof(buffer) - index; + } + + // Set bit adjustments + rxAlign = txLastBits; // Having a seperate variable is overkill. + // But it makes the next line easier to read. + PCD_WriteRegister( + BitFramingReg, + (rxAlign << 4) + + txLastBits); // RxAlign = BitFramingReg[6..4]. TxLastBits = + // BitFramingReg[2..0] + + // Transmit the buffer and receive the response. + result = PCD_TransceiveData(buffer, bufferUsed, responseBuffer, + &responseLength, &txLastBits, rxAlign); + if (result == STATUS_COLLISION) { // More than one PICC in the + // field => collision. + result = PCD_ReadRegister( + CollReg); // CollReg[7..0] bits are: ValuesAfterColl + // reserved CollPosNotValid CollPos[4:0] + if (result & 0x20) { // CollPosNotValid + return STATUS_COLLISION; // Without a valid collision + // position we cannot continue + } + byte collisionPos = + result & 0x1F; // Values 0-31, 0 means bit 32. + if (collisionPos == 0) { + collisionPos = 32; + } + if (collisionPos <= + currentLevelKnownBits) { // No progress - should not happen + return STATUS_INTERNAL_ERROR; + } + // Choose the PICC with the bit set. + currentLevelKnownBits = collisionPos; + count = (currentLevelKnownBits - 1) % 8; // The bit to modify + index = 1 + (currentLevelKnownBits / 8) + + (count ? 1 : 0); // First byte is index 0. + buffer[index] |= (1 << count); + } else if (result != STATUS_OK) { + return result; + } else { // STATUS_OK + if (currentLevelKnownBits >= 32) { // This was a SELECT. + selectDone = true; // No more anticollision + // We continue below outside the while. + } else { // This was an ANTICOLLISION. + // We now have all 32 bits of the UID in this Cascade Level + currentLevelKnownBits = 32; + // Run loop again to do the SELECT. + } + } + } // End of while (!selectDone) + + // We do not check the CBB - it was constructed by us above. + + // Copy the found UID bytes from buffer[] to uid->uidByte[] + index = (buffer[2] == PICC_CMD_CT) ? 3 : 2; // source index in buffer[] + bytesToCopy = (buffer[2] == PICC_CMD_CT) ? 3 : 4; + for (count = 0; count < bytesToCopy; count++) { + uid->uidByte[uidIndex + count] = buffer[index++]; + } + + // Check response SAK (Select Acknowledge) + if (responseLength != 3 || + txLastBits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A). + return STATUS_ERROR; + } + // Verify CRC_A - do our own calculation and store the control in + // buffer[2..3] - those bytes are not needed anymore. + result = PCD_CalculateCRC(responseBuffer, 1, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + if ((buffer[2] != responseBuffer[1]) || + (buffer[3] != responseBuffer[2])) { + return STATUS_CRC_WRONG; + } + if (responseBuffer[0] & + 0x04) { // Cascade bit set - UID not complete yes + cascadeLevel++; + } else { + uidComplete = true; + uid->sak = responseBuffer[0]; + } + } // End of while (!uidComplete) + + // Set correct uid->size + uid->size = 3 * cascadeLevel + 1; + + return STATUS_OK; +} // End PICC_Select() + +/** + * Instructs a PICC in state ACTIVE(*) to go to state HALT. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PICC_HaltA() { + byte result; + byte buffer[4]; + + // Build command buffer + buffer[0] = PICC_CMD_HLTA; + buffer[1] = 0; + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 2, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + + // Send the command. + // The standard says: + // If the PICC responds with any modulation during a period of 1 ms + // after the end of the frame containing the HLTA command, this + // response shall be interpreted as 'not acknowledge'. + // We interpret that this way: Only STATUS_TIMEOUT is an success. + result = PCD_TransceiveData(buffer, sizeof(buffer), NULL, 0); + if (result == STATUS_TIMEOUT) { + return STATUS_OK; + } + if (result == STATUS_OK) { // That is ironically NOT ok in this case ;-) + return STATUS_ERROR; + } + return result; +} // End PICC_HaltA() + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for communicating with MIFARE PICCs +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Executes the MFRC522 MFAuthent command. + * This command manages MIFARE authentication to enable a secure communication + * to any MIFARE Mini, MIFARE 1K and MIFARE 4K card. The authentication is + * described in the MFRC522 datasheet section 10.3.1.9 and + * http://www.nxp.com/documents/data_sheet/MF1S503x.pdf section 10.1. For use + * with MIFARE Classic PICCs. The PICC must be selected - ie in state ACTIVE(*) + * - before calling this function. Remember to call PCD_StopCrypto1() after + * communicating with the authenticated PICC - otherwise no new communications + * can start. + * + * All keys are set to FFFFFFFFFFFFh at chip delivery. + * + * @return STATUS_OK on success, STATUS_??? otherwise. Probably STATUS_TIMEOUT + * if you supply the wrong key. + */ +byte MFRC522::PCD_Authenticate( + byte command, ///< PICC_CMD_MF_AUTH_KEY_A or PICC_CMD_MF_AUTH_KEY_B + byte blockAddr, ///< The block number. See numbering in the comments in the + ///< .h file. + MIFARE_Key *key, ///< Pointer to the Crypteo1 key to use (6 bytes) + Uid *uid ///< Pointer to Uid struct. The first 4 bytes of the UID is used. +) { + byte waitIRq = 0x10; // IdleIRq + + // Build command buffer + byte sendData[12]; + sendData[0] = command; + sendData[1] = blockAddr; + for (byte i = 0; i < MF_KEY_SIZE; i++) { // 6 key bytes + sendData[2 + i] = key->keyByte[i]; + } + for (byte i = 0; i < 4; i++) { // The first 4 bytes of the UID + sendData[8 + i] = uid->uidByte[i]; + } + + // Start the authentication. + return PCD_CommunicateWithPICC(PCD_MFAuthent, waitIRq, &sendData[0], + sizeof(sendData)); +} // End PCD_Authenticate() + +/** + * Used to exit the PCD from its authenticated state. + * Remember to call this function after communicating with an authenticated PICC + * - otherwise no new communications can start. + */ +void MFRC522::PCD_StopCrypto1() { + // Clear MFCrypto1On bit + PCD_ClearRegisterBitMask( + Status2Reg, + 0x08); // Status2Reg[7..0] bits are: TempSensClear I2CForceHS reserved + // reserved MFCrypto1On ModemState[2:0] +} // End PCD_StopCrypto1() + +/** + * Reads 16 bytes (+ 2 bytes CRC_A) from the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated + * before calling this function. + * + * For MIFARE Ultralight only addresses 00h to 0Fh are decoded. + * The MF0ICU1 returns a NAK for higher addresses. + * The MF0ICU1 responds to the READ command by sending 16 bytes starting from + * the page address defined by the command argument. For example; if blockAddr + * is 03h then pages 03h, 04h, 05h, 06h are returned. A roll-back is + * implemented: If blockAddr is 0Eh, then the contents of pages 0Eh, 0Fh, 00h + * and 01h are returned. + * + * The buffer must be at least 18 bytes because a CRC_A is also returned. + * Checks the CRC_A before returning STATUS_OK. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Read( + byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE + ///< Ultralight: The first page to return data from. + byte *buffer, ///< The buffer to store the data in + byte *bufferSize ///< Buffer size, at least 18 bytes. Also number of bytes + ///< returned if STATUS_OK. +) { + byte result; + + // Sanity check + if (buffer == NULL || *bufferSize < 18) { + return STATUS_NO_ROOM; + } + + // Build command buffer + buffer[0] = PICC_CMD_MF_READ; + buffer[1] = blockAddr; + // Calculate CRC_A + result = PCD_CalculateCRC(buffer, 2, &buffer[2]); + if (result != STATUS_OK) { + return result; + } + + // Transmit the buffer and receive the response, validate CRC_A. + return PCD_TransceiveData(buffer, 4, buffer, bufferSize, NULL, 0, true); +} // End MIFARE_Read() + +/** + * Writes 16 bytes to the active PICC. + * + * For MIFARE Classic the sector containing the block must be authenticated + * before calling this function. + * + * For MIFARE Ultralight the operation is called "COMPATIBILITY WRITE". + * Even though 16 bytes are transferred to the Ultralight PICC, only the least + * significant 4 bytes (bytes 0 to 3) are written to the specified address. It + * is recommended to set the remaining bytes 04h to 0Fh to all logic 0. + * * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Write( + byte blockAddr, ///< MIFARE Classic: The block (0-0xff) number. MIFARE + ///< Ultralight: The page (2-15) to write to. + byte *buffer, ///< The 16 bytes to write to the PICC + byte bufferSize ///< Buffer size, must be at least 16 bytes. Exactly 16 + ///< bytes are written. +) { + byte result; + + // Sanity check + if (buffer == NULL || bufferSize < 16) { + return STATUS_INVALID; + } + + // Mifare Classic protocol requires two communications to perform a write. + // Step 1: Tell the PICC we want to write to block blockAddr. + byte cmdBuffer[2]; + cmdBuffer[0] = PICC_CMD_MF_WRITE; + cmdBuffer[1] = blockAddr; + result = PCD_MIFARE_Transceive( + cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + // Step 2: Transfer the data + result = PCD_MIFARE_Transceive( + buffer, + bufferSize); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End MIFARE_Write() + +/** + * Writes a 4 byte page to the active MIFARE Ultralight PICC. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Ultralight_Write( + byte page, ///< The page (2-15) to write to. + byte *buffer, ///< The 4 bytes to write to the PICC + byte bufferSize ///< Buffer size, must be at least 4 bytes. Exactly 4 bytes + ///< are written. +) { + byte result; + + // Sanity check + if (buffer == NULL || bufferSize < 4) { + return STATUS_INVALID; + } + + // Build commmand buffer + byte cmdBuffer[6]; + cmdBuffer[0] = PICC_CMD_UL_WRITE; + cmdBuffer[1] = page; + memcpy(&cmdBuffer[2], buffer, 4); + + // Perform the write + result = PCD_MIFARE_Transceive( + cmdBuffer, 6); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + return STATUS_OK; +} // End MIFARE_Ultralight_Write() + +/** + * MIFARE Decrement subtracts the delta from the value of the addressed block, + * and stores the result in a volatile memory. For MIFARE Classic only. The + * sector containing the block must be authenticated before calling this + * function. Only for blocks in "value block" mode, ie with access bits [C1 C2 + * C3] = [110] or [001]. Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Decrement(byte blockAddr, ///< The block (0-0xff) number. + long delta ///< This number is subtracted from + ///< the value of block blockAddr. +) { + return MIFARE_TwoStepHelper(PICC_CMD_MF_DECREMENT, blockAddr, delta); +} // End MIFARE_Decrement() + +/** + * MIFARE Increment adds the delta to the value of the addressed block, and + * stores the result in a volatile memory. For MIFARE Classic only. The sector + * containing the block must be authenticated before calling this function. Only + * for blocks in "value block" mode, ie with access bits [C1 C2 C3] = [110] or + * [001]. Use MIFARE_Transfer() to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Increment( + byte blockAddr, ///< The block (0-0xff) number. + long delta ///< This number is added to the value of block blockAddr. +) { + return MIFARE_TwoStepHelper(PICC_CMD_MF_INCREMENT, blockAddr, delta); +} // End MIFARE_Increment() + +/** + * MIFARE Restore copies the value of the addressed block into a volatile + * memory. For MIFARE Classic only. The sector containing the block must be + * authenticated before calling this function. Only for blocks in "value block" + * mode, ie with access bits [C1 C2 C3] = [110] or [001]. Use MIFARE_Transfer() + * to store the result in a block. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Restore(byte blockAddr ///< The block (0-0xff) number. +) { + // The datasheet describes Restore as a two step operation, but does not + // explain what data to transfer in step 2. Doing only a single step does + // not work, so I chose to transfer 0L in step two. + return MIFARE_TwoStepHelper(PICC_CMD_MF_RESTORE, blockAddr, 0L); +} // End MIFARE_Restore() + +/** + * Helper function for the two-step MIFARE Classic protocol operations + * Decrement, Increment and Restore. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_TwoStepHelper( + byte command, ///< The command to use + byte blockAddr, ///< The block (0-0xff) number. + long data ///< The data to transfer in step 2 +) { + byte result; + byte cmdBuffer[2]; // We only need room for 2 bytes. + + // Step 1: Tell the PICC the command and block address + cmdBuffer[0] = command; + cmdBuffer[1] = blockAddr; + result = PCD_MIFARE_Transceive( + cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + + // Step 2: Transfer the data + result = PCD_MIFARE_Transceive( + (byte *)&data, 4, true); // Adds CRC_A and accept timeout as success. + if (result != STATUS_OK) { + return result; + } + + return STATUS_OK; +} // End MIFARE_TwoStepHelper() + +/** + * MIFARE Transfer writes the value stored in the volatile memory into one + * MIFARE Classic block. For MIFARE Classic only. The sector containing the + * block must be authenticated before calling this function. Only for blocks in + * "value block" mode, ie with access bits [C1 C2 C3] = [110] or [001]. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_Transfer(byte blockAddr ///< The block (0-0xff) number. +) { + byte result; + byte cmdBuffer[2]; // We only need room for 2 bytes. + + // Tell the PICC we want to transfer the result into block blockAddr. + cmdBuffer[0] = PICC_CMD_MF_TRANSFER; + cmdBuffer[1] = blockAddr; + result = PCD_MIFARE_Transceive( + cmdBuffer, 2); // Adds CRC_A and checks that the response is MF_ACK. + if (result != STATUS_OK) { + return result; + } + return STATUS_OK; +} // End MIFARE_Transfer() + +/** + * Helper routine to read the current value from a Value Block. + * + * Only for MIFARE Classic and only for blocks in "value block" mode, that + * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing + * the block must be authenticated before calling this function. + * + * @param[in] blockAddr The block (0x00-0xff) number. + * @param[out] value Current value of the Value Block. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_GetValue(byte blockAddr, long *value) { + byte status; + byte buffer[18]; + byte size = sizeof(buffer); + + // Read the block + status = MIFARE_Read(blockAddr, buffer, &size); + if (status == STATUS_OK) { + // Extract the value + *value = (long(buffer[3]) << 24) | (long(buffer[2]) << 16) | + (long(buffer[1]) << 8) | long(buffer[0]); + } + return status; +} // End MIFARE_GetValue() + +/** + * Helper routine to write a specific value into a Value Block. + * + * Only for MIFARE Classic and only for blocks in "value block" mode, that + * is: with access bits [C1 C2 C3] = [110] or [001]. The sector containing + * the block must be authenticated before calling this function. + * + * @param[in] blockAddr The block (0x00-0xff) number. + * @param[in] value New value of the Value Block. + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::MIFARE_SetValue(byte blockAddr, long value) { + byte buffer[18]; + + // Translate the long into 4 bytes; repeated 2x in value block + buffer[0] = buffer[8] = (value & 0xFF); + buffer[1] = buffer[9] = (value & 0xFF00) >> 8; + buffer[2] = buffer[10] = (value & 0xFF0000) >> 16; + buffer[3] = buffer[11] = (value & 0xFF000000) >> 24; + // Inverse 4 bytes also found in value block + buffer[4] = ~buffer[0]; + buffer[5] = ~buffer[1]; + buffer[6] = ~buffer[2]; + buffer[7] = ~buffer[3]; + // Address 2x with inverse address 2x + buffer[12] = buffer[14] = blockAddr; + buffer[13] = buffer[15] = ~blockAddr; + + // Write the whole data block + return MIFARE_Write(blockAddr, buffer, 16); +} // End MIFARE_SetValue() + +///////////////////////////////////////////////////////////////////////////////////// +// Support functions +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Wrapper for MIFARE protocol communication. + * Adds CRC_A, executes the Transceive command and checks that the response is + * MF_ACK or a timeout. + * + * @return STATUS_OK on success, STATUS_??? otherwise. + */ +byte MFRC522::PCD_MIFARE_Transceive( + byte *sendData, ///< Pointer to the data to transfer to the FIFO. Do NOT + ///< include the CRC_A. + byte sendLen, ///< Number of bytes in sendData. + bool acceptTimeout ///< True => A timeout is also success +) { + byte result; + byte cmdBuffer[18]; // We need room for 16 bytes data and 2 bytes CRC_A. + + // Sanity check + if (sendData == NULL || sendLen > 16) { + return STATUS_INVALID; + } + + // Copy sendData[] to cmdBuffer[] and add CRC_A + memcpy(cmdBuffer, sendData, sendLen); + result = PCD_CalculateCRC(cmdBuffer, sendLen, &cmdBuffer[sendLen]); + if (result != STATUS_OK) { + return result; + } + sendLen += 2; + + // Transceive the data, store the reply in cmdBuffer[] + byte waitIRq = 0x30; // RxIRq and IdleIRq + byte cmdBufferSize = sizeof(cmdBuffer); + byte validBits = 0; + result = + PCD_CommunicateWithPICC(PCD_Transceive, waitIRq, cmdBuffer, sendLen, + cmdBuffer, &cmdBufferSize, &validBits); + if (acceptTimeout && result == STATUS_TIMEOUT) { + return STATUS_OK; + } + if (result != STATUS_OK) { + return result; + } + // The PICC must reply with a 4 bit ACK + if (cmdBufferSize != 1 || validBits != 4) { + return STATUS_ERROR; + } + if (cmdBuffer[0] != MF_ACK) { + return STATUS_MIFARE_NACK; + } + return STATUS_OK; +} // End PCD_MIFARE_Transceive() + +/** + * Returns a __FlashStringHelper pointer to a status code name. + * + * @return const __FlashStringHelper * + */ +const __FlashStringHelper *MFRC522::GetStatusCodeName( + byte code ///< One of the StatusCode enums. +) { + switch (code) { + case STATUS_OK: + return F("Success."); + break; + case STATUS_ERROR: + return F("Error in communication."); + break; + case STATUS_COLLISION: + return F("Collission detected."); + break; + case STATUS_TIMEOUT: + return F("Timeout in communication."); + break; + case STATUS_NO_ROOM: + return F("A buffer is not big enough."); + break; + case STATUS_INTERNAL_ERROR: + return F("Internal error in the code. Should not happen."); + break; + case STATUS_INVALID: + return F("Invalid argument."); + break; + case STATUS_CRC_WRONG: + return F("The CRC_A does not match."); + break; + case STATUS_MIFARE_NACK: + return F("A MIFARE PICC responded with NAK."); + break; + default: + return F("Unknown error"); + break; + } +} // End GetStatusCodeName() + +/** + * Translates the SAK (Select Acknowledge) to a PICC type. + * + * @return PICC_Type + */ +byte MFRC522::PICC_GetType( + byte sak ///< The SAK byte returned from PICC_Select(). +) { + if (sak & 0x04) { // UID not complete + return PICC_TYPE_NOT_COMPLETE; + } + + switch (sak) { + case 0x09: + return PICC_TYPE_MIFARE_MINI; + break; + case 0x08: + return PICC_TYPE_MIFARE_1K; + break; + case 0x18: + return PICC_TYPE_MIFARE_4K; + break; + case 0x00: + return PICC_TYPE_MIFARE_UL; + break; + case 0x10: + case 0x11: + return PICC_TYPE_MIFARE_PLUS; + break; + case 0x01: + return PICC_TYPE_TNP3XXX; + break; + default: + break; + } + + if (sak & 0x20) { + return PICC_TYPE_ISO_14443_4; + } + + if (sak & 0x40) { + return PICC_TYPE_ISO_18092; + } + + return PICC_TYPE_UNKNOWN; +} // End PICC_GetType() + +/** + * Returns a __FlashStringHelper pointer to the PICC type name. + * + * @return const __FlashStringHelper * + */ +const __FlashStringHelper *MFRC522::PICC_GetTypeName( + byte piccType ///< One of the PICC_Type enums. +) { + switch (piccType) { + case PICC_TYPE_ISO_14443_4: + return F("PICC compliant with ISO/IEC 14443-4"); + break; + case PICC_TYPE_ISO_18092: + return F("PICC compliant with ISO/IEC 18092 (NFC)"); + break; + case PICC_TYPE_MIFARE_MINI: + return F("MIFARE Mini, 320 bytes"); + break; + case PICC_TYPE_MIFARE_1K: + return F("MIFARE 1KB"); + break; + case PICC_TYPE_MIFARE_4K: + return F("MIFARE 4KB"); + break; + case PICC_TYPE_MIFARE_UL: + return F("MIFARE Ultralight or Ultralight C"); + break; + case PICC_TYPE_MIFARE_PLUS: + return F("MIFARE Plus"); + break; + case PICC_TYPE_TNP3XXX: + return F("MIFARE TNP3XXX"); + break; + case PICC_TYPE_NOT_COMPLETE: + return F("SAK indicates UID is not complete."); + break; + case PICC_TYPE_UNKNOWN: + default: + return F("Unknown type"); + break; + } +} // End PICC_GetTypeName() + +/** + * Dumps debug info about the selected PICC to Serial. + * On success the PICC is halted after dumping the data. + * For MIFARE Classic the factory default key of 0xFFFFFFFFFFFF is tried. + */ +void MFRC522::PICC_DumpToSerial(Uid *uid ///< Pointer to Uid struct returned + ///< from a successful PICC_Select(). +) { + MIFARE_Key key; + + // UID + Serial.print(F("Card UID:")); + for (byte i = 0; i < uid->size; i++) { + if (uid->uidByte[i] < 0x10) + Serial.print(F(" 0")); + else + Serial.print(F(" ")); + Serial.print(uid->uidByte[i], HEX); + } + Serial.println(); + + // PICC type + byte piccType = PICC_GetType(uid->sak); + Serial.print(F("PICC type: ")); + Serial.println(PICC_GetTypeName(piccType)); + + // Dump contents + switch (piccType) { + case PICC_TYPE_MIFARE_MINI: + case PICC_TYPE_MIFARE_1K: + case PICC_TYPE_MIFARE_4K: + // All keys are set to FFFFFFFFFFFFh at chip delivery from the + // factory. + for (byte i = 0; i < 6; i++) { + key.keyByte[i] = 0xFF; + } + PICC_DumpMifareClassicToSerial(uid, piccType, &key); + break; + + case PICC_TYPE_MIFARE_UL: + PICC_DumpMifareUltralightToSerial(); + break; + + case PICC_TYPE_ISO_14443_4: + case PICC_TYPE_ISO_18092: + case PICC_TYPE_MIFARE_PLUS: + case PICC_TYPE_TNP3XXX: + Serial.println(F( + "Dumping memory contents not implemented for that PICC type.")); + break; + + case PICC_TYPE_UNKNOWN: + case PICC_TYPE_NOT_COMPLETE: + default: + break; // No memory dump here + } + + Serial.println(); + PICC_HaltA(); // Already done if it was a MIFARE Classic PICC. +} // End PICC_DumpToSerial() + +/** + * Dumps memory contents of a MIFARE Classic PICC. + * On success the PICC is halted after dumping the data. + */ +void MFRC522::PICC_DumpMifareClassicToSerial( + Uid *uid, ///< Pointer to Uid struct returned from a successful + ///< PICC_Select(). + byte piccType, ///< One of the PICC_Type enums. + MIFARE_Key *key ///< Key A used for all sectors. +) { + byte no_of_sectors = 0; + switch (piccType) { + case PICC_TYPE_MIFARE_MINI: + // Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. + no_of_sectors = 5; + break; + + case PICC_TYPE_MIFARE_1K: + // Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. + no_of_sectors = 16; + break; + + case PICC_TYPE_MIFARE_4K: + // Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) + // * 16 bytes/block = 4096 bytes. + no_of_sectors = 40; + break; + + default: // Should not happen. Ignore. + break; + } + + // Dump sectors, highest address first. + if (no_of_sectors) { + Serial.println( + F("Sector Block 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 " + "15 AccessBits")); + for (int8_t i = no_of_sectors - 1; i >= 0; i--) { + PICC_DumpMifareClassicSectorToSerial(uid, key, i); + } + } + PICC_HaltA(); // Halt the PICC before stopping the encrypted session. + PCD_StopCrypto1(); +} // End PICC_DumpMifareClassicToSerial() + +/** + * Dumps memory contents of a sector of a MIFARE Classic PICC. + * Uses PCD_Authenticate(), MIFARE_Read() and PCD_StopCrypto1. + * Always uses PICC_CMD_MF_AUTH_KEY_A because only Key A can always read the + * sector trailer access bits. + */ +void MFRC522::PICC_DumpMifareClassicSectorToSerial( + Uid *uid, ///< Pointer to Uid struct returned from a successful + ///< PICC_Select(). + MIFARE_Key *key, ///< Key A for the sector. + byte sector ///< The sector to dump, 0..39. +) { + byte status; + byte firstBlock; // Address of lowest address to dump actually last block + // dumped) + byte no_of_blocks; // Number of blocks in sector + bool isSectorTrailer; // Set to true while handling the "last" (ie highest + // address) in the sector. + + // The access bits are stored in a peculiar fashion. + // There are four groups: + // g[3] Access bits for the sector trailer, block 3 (for sectors + // 0-31) or block 15 (for sectors 32-39) g[2] Access bits for + // block 2 (for sectors 0-31) or blocks 10-14 (for sectors 32-39) g[1] + // Access bits for block 1 (for sectors 0-31) or blocks 5-9 (for sectors + // 32-39) g[0] Access bits for block 0 (for sectors 0-31) or blocks + // 0-4 (for sectors 32-39) + // Each group has access bits [C1 C2 C3]. In this code C1 is MSB and C3 is + // LSB. The four CX bits are stored together in a nible cx and an inverted + // nible cx_. + byte c1, c2, c3; // Nibbles + byte c1_, c2_, c3_; // Inverted nibbles + bool invertedError; // True if one of the inverted nibbles did not match + byte g[4]; // Access bits for each of the four groups. + byte group; // 0-3 - active group for access bits + bool firstInGroup; // True for the first block dumped in the group + + // Determine position and size of sector. + if (sector < 32) { // Sectors 0..31 has 4 blocks each + no_of_blocks = 4; + firstBlock = sector * no_of_blocks; + } else if (sector < 40) { // Sectors 32-39 has 16 blocks each + no_of_blocks = 16; + firstBlock = 128 + (sector - 32) * no_of_blocks; + } else { // Illegal input, no MIFARE Classic PICC has more than 40 sectors. + return; + } + + // Dump blocks, highest address first. + byte byteCount; + byte buffer[18]; + byte blockAddr; + isSectorTrailer = true; + for (int8_t blockOffset = no_of_blocks - 1; blockOffset >= 0; + blockOffset--) { + blockAddr = firstBlock + blockOffset; + // Sector number - only on first line + if (isSectorTrailer) { + if (sector < 10) + Serial.print(F(" ")); // Pad with spaces + else + Serial.print(F(" ")); // Pad with spaces + Serial.print(sector); + Serial.print(F(" ")); + } else { + Serial.print(F(" ")); + } + // Block number + if (blockAddr < 10) + Serial.print(F(" ")); // Pad with spaces + else { + if (blockAddr < 100) + Serial.print(F(" ")); // Pad with spaces + else + Serial.print(F(" ")); // Pad with spaces + } + Serial.print(blockAddr); + Serial.print(F(" ")); + // Establish encrypted communications before reading the first block + if (isSectorTrailer) { + status = + PCD_Authenticate(PICC_CMD_MF_AUTH_KEY_A, firstBlock, key, uid); + if (status != STATUS_OK) { + Serial.print(F("PCD_Authenticate() failed: ")); + Serial.println(GetStatusCodeName(status)); + return; + } + } + // Read block + byteCount = sizeof(buffer); + status = MIFARE_Read(blockAddr, buffer, &byteCount); + if (status != STATUS_OK) { + Serial.print(F("MIFARE_Read() failed: ")); + Serial.println(GetStatusCodeName(status)); + continue; + } + // Dump data + for (byte index = 0; index < 16; index++) { + if (buffer[index] < 0x10) + Serial.print(F(" 0")); + else + Serial.print(F(" ")); + Serial.print(buffer[index], HEX); + if ((index % 4) == 3) { + Serial.print(F(" ")); + } + } + // Parse sector trailer data + if (isSectorTrailer) { + c1 = buffer[7] >> 4; + c2 = buffer[8] & 0xF; + c3 = buffer[8] >> 4; + c1_ = buffer[6] & 0xF; + c2_ = buffer[6] >> 4; + c3_ = buffer[7] & 0xF; + invertedError = (c1 != (~c1_ & 0xF)) || (c2 != (~c2_ & 0xF)) || + (c3 != (~c3_ & 0xF)); + g[0] = ((c1 & 1) << 2) | ((c2 & 1) << 1) | ((c3 & 1) << 0); + g[1] = ((c1 & 2) << 1) | ((c2 & 2) << 0) | ((c3 & 2) >> 1); + g[2] = ((c1 & 4) << 0) | ((c2 & 4) >> 1) | ((c3 & 4) >> 2); + g[3] = ((c1 & 8) >> 1) | ((c2 & 8) >> 2) | ((c3 & 8) >> 3); + isSectorTrailer = false; + } + + // Which access group is this block in? + if (no_of_blocks == 4) { + group = blockOffset; + firstInGroup = true; + } else { + group = blockOffset / 5; + firstInGroup = (group == 3) || (group != (blockOffset + 1) / 5); + } + + if (firstInGroup) { + // Print access bits + Serial.print(F(" [ ")); + Serial.print((g[group] >> 2) & 1, DEC); + Serial.print(F(" ")); + Serial.print((g[group] >> 1) & 1, DEC); + Serial.print(F(" ")); + Serial.print((g[group] >> 0) & 1, DEC); + Serial.print(F(" ] ")); + if (invertedError) { + Serial.print(F(" Inverted access bits did not match! ")); + } + } + + if (group != 3 && + (g[group] == 1 || + g[group] == 6)) { // Not a sector trailer, a value block + long value = (long(buffer[3]) << 24) | (long(buffer[2]) << 16) | + (long(buffer[1]) << 8) | long(buffer[0]); + Serial.print(F(" Value=0x")); + Serial.print(value, HEX); + Serial.print(F(" Adr=0x")); + Serial.print(buffer[12], HEX); + } + Serial.println(); + } + + return; +} // End PICC_DumpMifareClassicSectorToSerial() + +/** + * Dumps memory contents of a MIFARE Ultralight PICC. + */ +void MFRC522::PICC_DumpMifareUltralightToSerial() { + byte status; + byte byteCount; + byte buffer[18]; + byte i; + + Serial.println(F("Page 0 1 2 3")); + // Try the mpages of the original Ultralight. Ultralight C has more pages. + for (byte page = 0; page < 16; + page += 4) { // Read returns data for 4 pages at a time. + // Read pages + byteCount = sizeof(buffer); + status = MIFARE_Read(page, buffer, &byteCount); + if (status != STATUS_OK) { + Serial.print(F("MIFARE_Read() failed: ")); + Serial.println(GetStatusCodeName(status)); + break; + } + // Dump data + for (byte offset = 0; offset < 4; offset++) { + i = page + offset; + if (i < 10) + Serial.print(F(" ")); // Pad with spaces + else + Serial.print(F(" ")); // Pad with spaces + Serial.print(i); + Serial.print(F(" ")); + for (byte index = 0; index < 4; index++) { + i = 4 * offset + index; + if (buffer[i] < 0x10) + Serial.print(F(" 0")); + else + Serial.print(F(" ")); + Serial.print(buffer[i], HEX); + } + Serial.println(); + } + } +} // End PICC_DumpMifareUltralightToSerial() + +/** + * Calculates the bit pattern needed for the specified access bits. In the [C1 + * C2 C3] tupples C1 is MSB (=4) and C3 is LSB (=1). + */ +void MFRC522::MIFARE_SetAccessBits( + byte *accessBitBuffer, ///< Pointer to byte 6, 7 and 8 in the sector + ///< trailer. Bytes [0..2] will be set. + byte g0, ///< Access bits [C1 C2 C3] for block 0 (for sectors 0-31) or + ///< blocks 0-4 (for sectors 32-39) + byte g1, ///< Access bits C1 C2 C3] for block 1 (for sectors 0-31) or + ///< blocks 5-9 (for sectors 32-39) + byte g2, ///< Access bits C1 C2 C3] for block 2 (for sectors 0-31) or + ///< blocks 10-14 (for sectors 32-39) + byte g3 ///< Access bits C1 C2 C3] for the sector trailer, block 3 (for + ///< sectors 0-31) or block 15 (for sectors 32-39) +) { + byte c1 = + ((g3 & 4) << 1) | ((g2 & 4) << 0) | ((g1 & 4) >> 1) | ((g0 & 4) >> 2); + byte c2 = + ((g3 & 2) << 2) | ((g2 & 2) << 1) | ((g1 & 2) << 0) | ((g0 & 2) >> 1); + byte c3 = + ((g3 & 1) << 3) | ((g2 & 1) << 2) | ((g1 & 1) << 1) | ((g0 & 1) << 0); + + accessBitBuffer[0] = (~c2 & 0xF) << 4 | (~c1 & 0xF); + accessBitBuffer[1] = c1 << 4 | (~c3 & 0xF); + accessBitBuffer[2] = c3 << 4 | c2; +} // End MIFARE_SetAccessBits() + +/** + * Performs the "magic sequence" needed to get Chinese UID changeable + * Mifare cards to allow writing to sector 0, where the card UID is stored. + * + * Note that you do not need to have selected the card through REQA or WUPA, + * this sequence works immediately when the card is in the reader vicinity. + * This means you can use this method even on "bricked" cards that your reader + * does not recognise anymore (see MFRC522::MIFARE_UnbrickUidSector). + * + * Of course with non-bricked devices, you're free to select them before calling + * this function. + */ +bool MFRC522::MIFARE_OpenUidBackdoor(bool logErrors) { + // Magic sequence: + // > 50 00 57 CD (HALT + CRC) + // > 40 (7 bits only) + // < A (4 bits only) + // > 43 + // < A (4 bits only) + // Then you can write to sector 0 without authenticating + + PICC_HaltA(); // 50 00 57 CD + + byte cmd = 0x40; + byte validBits = + 7; /* Our command is only 7 bits. After receiving card response, + this will contain amount of valid response bits. */ + byte response[32]; // Card's response is written here + byte received; + byte status = PCD_TransceiveData(&cmd, (byte)1, response, &received, + &validBits, (byte)0, false); // 40 + if (status != STATUS_OK) { + if (logErrors) { + Serial.println( + F("Card did not respond to 0x40 after HALT command. Are you " + "sure it is a UID changeable one?")); + Serial.print(F("Error name: ")); + Serial.println(GetStatusCodeName(status)); + } + return false; + } + if (received != 1 || response[0] != 0x0A) { + if (logErrors) { + Serial.print(F("Got bad response on backdoor 0x40 command: ")); + Serial.print(response[0], HEX); + Serial.print(F(" (")); + Serial.print(validBits); + Serial.print(F(" valid bits)\r\n")); + } + return false; + } + + cmd = 0x43; + validBits = 8; + status = PCD_TransceiveData(&cmd, (byte)1, response, &received, &validBits, + (byte)0, false); // 43 + if (status != STATUS_OK) { + if (logErrors) { + Serial.println( + F("Error in communication at command 0x43, after successfully " + "executing 0x40")); + Serial.print(F("Error name: ")); + Serial.println(GetStatusCodeName(status)); + } + return false; + } + if (received != 1 || response[0] != 0x0A) { + if (logErrors) { + Serial.print(F("Got bad response on backdoor 0x43 command: ")); + Serial.print(response[0], HEX); + Serial.print(F(" (")); + Serial.print(validBits); + Serial.print(F(" valid bits)\r\n")); + } + return false; + } + + // You can now write to sector 0 without authenticating! + return true; +} // End MIFARE_OpenUidBackdoor() + +/** + * Reads entire block 0, including all manufacturer data, and overwrites + * that block with the new UID, a freshly calculated BCC, and the original + * manufacturer data. + * + * It assumes a default KEY A of 0xFFFFFFFFFFFF. + * Make sure to have selected the card before this function is called. + */ +bool MFRC522::MIFARE_SetUid(byte *newUid, byte uidSize, bool logErrors) { + // UID + BCC byte can not be larger than 16 together + if (!newUid || !uidSize || uidSize > 15) { + if (logErrors) { + Serial.println( + F("New UID buffer empty, size 0, or size > 15 given")); + } + return false; + } + + // Authenticate for reading + MIFARE_Key key = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + byte status = + PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, (byte)1, &key, &uid); + if (status != STATUS_OK) { + if (status == STATUS_TIMEOUT) { + // We get a read timeout if no card is selected yet, so let's select + // one + + // Wake the card up again if sleeping + // byte atqa_answer[2]; + // byte atqa_size = 2; + // PICC_WakeupA(atqa_answer, &atqa_size); + + if (!PICC_IsNewCardPresent() || !PICC_ReadCardSerial()) { + Serial.println( + F("No card was previously selected, and none are " + "available. Failed to set UID.")); + return false; + } + + status = PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, (byte)1, + &key, &uid); + if (status != STATUS_OK) { + // We tried, time to give up + if (logErrors) { + Serial.println( + F("Failed to authenticate to card for reading, could " + "not set UID: ")); + Serial.println(GetStatusCodeName(status)); + } + return false; + } + } else { + if (logErrors) { + Serial.print(F("PCD_Authenticate() failed: ")); + Serial.println(GetStatusCodeName(status)); + } + return false; + } + } + + // Read block 0 + byte block0_buffer[18]; + byte byteCount = sizeof(block0_buffer); + status = MIFARE_Read((byte)0, block0_buffer, &byteCount); + if (status != STATUS_OK) { + if (logErrors) { + Serial.print(F("MIFARE_Read() failed: ")); + Serial.println(GetStatusCodeName(status)); + Serial.println( + F("Are you sure your KEY A for sector 0 is 0xFFFFFFFFFFFF?")); + } + return false; + } + + // Write new UID to the data we just read, and calculate BCC byte + byte bcc = 0; + for (int i = 0; i < uidSize; i++) { + block0_buffer[i] = newUid[i]; + bcc ^= newUid[i]; + } + + // Write BCC byte to buffer + block0_buffer[uidSize] = bcc; + + // Stop encrypted traffic so we can send raw bytes + PCD_StopCrypto1(); + + // Activate UID backdoor + if (!MIFARE_OpenUidBackdoor(logErrors)) { + if (logErrors) { + Serial.println(F("Activating the UID backdoor failed.")); + } + return false; + } + + // Write modified block 0 back to card + status = MIFARE_Write((byte)0, block0_buffer, (byte)16); + if (status != STATUS_OK) { + if (logErrors) { + Serial.print(F("MIFARE_Write() failed: ")); + Serial.println(GetStatusCodeName(status)); + } + return false; + } + + // Wake the card up again + byte atqa_answer[2]; + byte atqa_size = 2; + PICC_WakeupA(atqa_answer, &atqa_size); + + return true; +} + +/** + * Resets entire sector 0 to zeroes, so the card can be read again by readers. + */ +bool MFRC522::MIFARE_UnbrickUidSector(bool logErrors) { + MIFARE_OpenUidBackdoor(logErrors); + + byte block0_buffer[] = {0x01, 0x02, 0x03, 0x04, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + // Write modified block 0 back to card + byte status = MIFARE_Write((byte)0, block0_buffer, (byte)16); + if (status != STATUS_OK) { + if (logErrors) { + Serial.print(F("MIFARE_Write() failed: ")); + Serial.println(GetStatusCodeName(status)); + } + return false; + } + return true; +} + +///////////////////////////////////////////////////////////////////////////////////// +// Convenience functions - does not add extra functionality +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns true if a PICC responds to PICC_CMD_REQA. + * Only "new" cards in state IDLE are invited. Sleeping cards in state HALT are + * ignored. + * + * @return bool + */ +bool MFRC522::PICC_IsNewCardPresent() { + byte bufferATQA[2]; + byte bufferSize = sizeof(bufferATQA); + byte result = PICC_RequestA(bufferATQA, &bufferSize); + return (result == STATUS_OK || result == STATUS_COLLISION); +} // End PICC_IsNewCardPresent() + +/** + * Simple wrapper around PICC_Select. + * Returns true if a UID could be read. + * Remember to call PICC_IsNewCardPresent(), PICC_RequestA() or PICC_WakeupA() + * first. The read UID is available in the class variable uid. + * + * @return bool + */ +bool MFRC522::PICC_ReadCardSerial() { + byte result = PICC_Select(&uid); + return (result == STATUS_OK); +} // End PICC_ReadCardSerial() \ No newline at end of file diff --git a/lib/NFC/src/MFRC522_I2C.h b/lib/NFC/src/MFRC522_I2C.h new file mode 100644 index 0000000..5324093 --- /dev/null +++ b/lib/NFC/src/MFRC522_I2C.h @@ -0,0 +1,472 @@ +/** + * MFRC522_I2C.h - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS + *I2C BY AROZCAN MFRC522_I2C.h - Based on ARDUINO RFID MODULE KIT 13.56 MHZ WITH + *TAGS SPI Library BY COOQROBOT. Based on code Dr.Leong ( WWW.B2CQSHOP.COM ) + * Created by Miguel Balboa (circuitito.com), Jan, 2012. + * Rewritten by Søren Thing Andersen (access.thing.dk), fall of 2013 + *(Translation to English, refactored, comments, anti collision, cascade + *levels.) Extended by Tom Clement with functionality to write to sector 0 of + *UID changeable Mifare cards. Extended by Ahmet Remzi Ozcan with I2C + *functionality. Author: arozcan @ + *https://github.com/arozcan/MFRC522-I2C-Library Released into the public + *domain. + * + * Please read this file for an overview and then MFRC522.cpp for comments on + *the specific functions. Search for "mf-rc522" on ebay.com to purchase the + *MF-RC522 board. + * + * There are three hardware components involved: + * 1) The micro controller: An Arduino + * 2) The PCD (short for Proximity Coupling Device): NXP MFRC522 Contactless + *Reader IC 3) The PICC (short for Proximity Integrated Circuit Card): A card or + *tag using the ISO 14443A interface, eg Mifare or NTAG203. + * + * The microcontroller and card reader uses I2C for communication. + * The protocol is described in the MFRC522 datasheet: + *http://www.nxp.com/documents/data_sheet/MFRC522.pdf + * + * The card reader and the tags communicate using a 13.56MHz electromagnetic + *field. The protocol is defined in ISO/IEC 14443-3 Identification cards -- + *Contactless integrated circuit cards -- Proximity cards -- Part 3: + *Initialization and anticollision". A free version of the final draft can be + *found at http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf Details are + *found in chapter 6, Type A – Initialization and anticollision. + * + * If only the PICC UID is wanted, the above documents has all the needed + *information. To read and write from MIFARE PICCs, the MIFARE protocol is used + *after the PICC has been selected. The MIFARE Classic chips and protocol is + *described in the datasheets: 1K: + *http://www.nxp.com/documents/data_sheet/MF1S503x.pdf 4K: + *http://www.nxp.com/documents/data_sheet/MF1S703x.pdf Mini: + *http://www.idcardmarket.com/download/mifare_S20_datasheet.pdf The MIFARE + *Ultralight chip and protocol is described in the datasheets: Ultralight: + *http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf Ultralight C: + *http://www.nxp.com/documents/short_data_sheet/MF0ICU2_SDS.pdf + * + * MIFARE Classic 1K (MF1S503x): + * Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes. + * The blocks are numbered 0-63. + * Block 3 in each sector is the Sector Trailer. See + *http://www.nxp.com/documents/data_sheet/MF1S503x.pdf sections 8.6 and 8.7: + * Bytes 0-5: Key A + * Bytes 6-8: Access Bits + * Bytes 9: User data + * Bytes 10-15: Key B (or user data) + * Block 0 is read-only manufacturer data. + * To access a block, an authentication using a key from the block's sector + *must be performed first. Example: To read from block 10, first authenticate + *using a key from sector 3 (blocks 8-11). All keys are set to FFFFFFFFFFFFh at + *chip delivery. Warning: Please read section 8.7 "Memory Access". It includes + *this text: if the PICC detects a format violation the whole sector is + *irreversibly blocked. To use a block in "value block" mode (for + *Increment/Decrement operations) you need to change the sector trailer. Use + *PICC_SetAccessBits() to calculate the bit patterns. MIFARE Classic 4K + *(MF1S703x): Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) + ** 16 bytes/block = 4096 bytes. The blocks are numbered 0-255. The last block + *in each sector is the Sector Trailer like above. MIFARE Classic Mini (MF1 IC + *S20): Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes. The blocks + *are numbered 0-19. The last block in each sector is the Sector Trailer like + *above. + * + * MIFARE Ultralight (MF0ICU1): + * Has 16 pages of 4 bytes = 64 bytes. + * Pages 0 + 1 is used for the 7-byte UID. + * Page 2 contains the last check digit for the UID, one byte manufacturer + *internal data, and the lock bytes (see + *http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) Page 3 is + *OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. Pages + *4-15 are read/write unless blocked by the lock bytes in page 2. MIFARE + *Ultralight C (MF0ICU2): Has 48 pages of 4 bytes = 192 bytes. Pages 0 + 1 is + *used for the 7-byte UID. Page 2 contains the last check digit for the UID, one + *byte manufacturer internal data, and the lock bytes (see + *http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2) Page 3 is + *OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0. Pages + *4-39 are read/write unless blocked by the lock bytes in page 2. Page 40 Lock + *bytes Page 41 16 bit one way counter Pages 42-43 Authentication configuration + * Pages 44-47 Authentication key + */ +#ifndef MFRC522_h +#define MFRC522_h + +#include +#include + +// Firmware data for self-test +// Reference values based on firmware version +// Hint: if needed, you can remove unused self-test data to save flash memory +// +// Version 0.0 (0x90) +// Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August +// 2005; 16.1 Sefttest +const byte MFRC522_firmware_referenceV0_0[] PROGMEM = { + 0x00, 0x87, 0x98, 0x0f, 0x49, 0xFF, 0x07, 0x19, 0xBF, 0x22, 0x30, + 0x49, 0x59, 0x63, 0xAD, 0xCA, 0x7F, 0xE3, 0x4E, 0x03, 0x5C, 0x4E, + 0x49, 0x50, 0x47, 0x9A, 0x37, 0x61, 0xE7, 0xE2, 0xC6, 0x2E, 0x75, + 0x5A, 0xED, 0x04, 0x3D, 0x02, 0x4B, 0x78, 0x32, 0xFF, 0x58, 0x3B, + 0x7C, 0xE9, 0x00, 0x94, 0xB4, 0x4A, 0x59, 0x5B, 0xFD, 0xC9, 0x29, + 0xDF, 0x35, 0x96, 0x98, 0x9E, 0x4F, 0x30, 0x32, 0x8D}; +// Version 1.0 (0x91) +// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 Self test +const byte MFRC522_firmware_referenceV1_0[] PROGMEM = { + 0x00, 0xC6, 0x37, 0xD5, 0x32, 0xB7, 0x57, 0x5C, 0xC2, 0xD8, 0x7C, + 0x4D, 0xD9, 0x70, 0xC7, 0x73, 0x10, 0xE6, 0xD2, 0xAA, 0x5E, 0xA1, + 0x3E, 0x5A, 0x14, 0xAF, 0x30, 0x61, 0xC9, 0x70, 0xDB, 0x2E, 0x64, + 0x22, 0x72, 0xB5, 0xBD, 0x65, 0xF4, 0xEC, 0x22, 0xBC, 0xD3, 0x72, + 0x35, 0xCD, 0xAA, 0x41, 0x1F, 0xA7, 0xF3, 0x53, 0x14, 0xDE, 0x7E, + 0x02, 0xD9, 0x0F, 0xB5, 0x5E, 0x25, 0x1D, 0x29, 0x79}; +// Version 2.0 (0x92) +// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 Self test +const byte MFRC522_firmware_referenceV2_0[] PROGMEM = { + 0x00, 0xEB, 0x66, 0xBA, 0x57, 0xBF, 0x23, 0x95, 0xD0, 0xE3, 0x0D, + 0x3D, 0x27, 0x89, 0x5C, 0xDE, 0x9D, 0x3B, 0xA7, 0x00, 0x21, 0x5B, + 0x89, 0x82, 0x51, 0x3A, 0xEB, 0x02, 0x0C, 0xA5, 0x00, 0x49, 0x7C, + 0x84, 0x4D, 0xB3, 0xCC, 0xD2, 0x1B, 0x81, 0x5D, 0x48, 0x76, 0xD5, + 0x71, 0x61, 0x21, 0xA9, 0x86, 0x96, 0x83, 0x38, 0xCF, 0x9D, 0x5B, + 0x6D, 0xDC, 0x15, 0xBA, 0x3E, 0x7D, 0x95, 0x3B, 0x2F}; +// Clone +// Fudan Semiconductor FM17522 (0x88) +const byte FM17522_firmware_reference[] PROGMEM = { + 0x00, 0xD6, 0x78, 0x8C, 0xE2, 0xAA, 0x0C, 0x18, 0x2A, 0xB8, 0x7A, + 0x7F, 0xD3, 0x6A, 0xCF, 0x0B, 0xB1, 0x37, 0x63, 0x4B, 0x69, 0xAE, + 0x91, 0xC7, 0xC3, 0x97, 0xAE, 0x77, 0xF4, 0x37, 0xD7, 0x9B, 0x7C, + 0xF5, 0x3C, 0x11, 0x8F, 0x15, 0xC3, 0xD7, 0xC1, 0x5B, 0x00, 0x2A, + 0xD0, 0x75, 0xDE, 0x9E, 0x51, 0x64, 0xAB, 0x3E, 0xE9, 0x15, 0xB5, + 0xAB, 0x56, 0x9A, 0x98, 0x82, 0x26, 0xEA, 0x2A, 0x62}; + +class MFRC522 { + public: + // MFRC522 registers. Described in chapter 9 of the datasheet. + enum PCD_Register { + // Page 0: Command and status + // 0x00 // reserved for future use + CommandReg = 0x01, // starts and stops command execution + ComIEnReg = 0x02, // enable and disable interrupt request control bits + DivIEnReg = 0x03, // enable and disable interrupt request control bits + ComIrqReg = 0x04, // interrupt request bits + DivIrqReg = 0x05, // interrupt request bits + ErrorReg = 0x06, // error bits showing the error status of the last + // command executed + Status1Reg = 0x07, // communication status bits + Status2Reg = 0x08, // receiver and transmitter status bits + FIFODataReg = 0x09, // input and output of 64 byte FIFO buffer + FIFOLevelReg = 0x0A, // number of bytes stored in the FIFO buffer + WaterLevelReg = 0x0B, // level for FIFO underflow and overflow warning + ControlReg = 0x0C, // miscellaneous control registers + BitFramingReg = 0x0D, // adjustments for bit-oriented frames + CollReg = 0x0E, // bit position of the first bit-collision detected on + // the RF interface + // 0x0F // reserved for future use + + // Page 1: Command + // 0x10 // reserved for future use + ModeReg = 0x11, // defines general modes for transmitting and receiving + TxModeReg = 0x12, // defines transmission data rate and framing + RxModeReg = 0x13, // defines reception data rate and framing + TxControlReg = 0x14, // controls the logical behavior of the antenna + // driver pins TX1 and TX2 + TxASKReg = 0x15, // controls the setting of the transmission modulation + TxSelReg = 0x16, // selects the internal sources for the antenna driver + RxSelReg = 0x17, // selects internal receiver settings + RxThresholdReg = 0x18, // selects thresholds for the bit decoder + DemodReg = 0x19, // defines demodulator settings + // 0x1A // reserved for future use + // 0x1B // reserved for future use + MfTxReg = + 0x1C, // controls some MIFARE communication transmit parameters + MfRxReg = + 0x1D, // controls some MIFARE communication receive parameters + // 0x1E // reserved for future use + SerialSpeedReg = + 0x1F, // selects the speed of the serial UART interface + + // Page 2: Configuration + // 0x20 // reserved for future use + CRCResultRegH = + 0x21, // shows the MSB and LSB values of the CRC calculation + CRCResultRegL = 0x22, + // 0x23 // reserved for future use + ModWidthReg = 0x24, // controls the ModWidth setting? + // 0x25 // reserved for future use + RFCfgReg = 0x26, // configures the receiver gain + GsNReg = 0x27, // selects the conductance of the antenna driver pins + // TX1 and TX2 for modulation + CWGsPReg = 0x28, // defines the conductance of the p-driver output + // during periods of no modulation + ModGsPReg = 0x29, // defines the conductance of the p-driver output + // during periods of modulation + TModeReg = 0x2A, // defines settings for the internal timer + TPrescalerReg = 0x2B, // the lower 8 bits of the TPrescaler value. The + // 4 high bits are in TModeReg. + TReloadRegH = 0x2C, // defines the 16-bit timer reload value + TReloadRegL = 0x2D, + TCounterValueRegH = 0x2E, // shows the 16-bit timer value + TCounterValueRegL = 0x2F, + + // Page 3: Test Registers + // 0x30 // reserved for future use + TestSel1Reg = 0x31, // general test signal configuration + TestSel2Reg = 0x32, // general test signal configuration + TestPinEnReg = 0x33, // enables pin output driver on pins D1 to D7 + TestPinValueReg = 0x34, // defines the values for D1 to D7 when it is + // used as an I/O bus + TestBusReg = 0x35, // shows the status of the internal test bus + AutoTestReg = 0x36, // controls the digital self test + VersionReg = 0x37, // shows the software version + AnalogTestReg = 0x38, // controls the pins AUX1 and AUX2 + TestDAC1Reg = 0x39, // defines the test value for TestDAC1 + TestDAC2Reg = 0x3A, // defines the test value for TestDAC2 + TestADCReg = 0x3B // shows the value of ADC I and Q channels + // 0x3C // reserved for production tests + // 0x3D // reserved for production tests + // 0x3E // reserved for production tests + // 0x3F // reserved for production tests + }; + + // MFRC522 commands. Described in chapter 10 of the datasheet. + enum PCD_Command { + PCD_Idle = 0x00, // no action, cancels current command execution + PCD_Mem = 0x01, // stores 25 bytes into the internal buffer + PCD_GenerateRandomID = 0x02, // generates a 10-byte random ID number + PCD_CalcCRC = + 0x03, // activates the CRC coprocessor or performs a self test + PCD_Transmit = 0x04, // transmits data from the FIFO buffer + PCD_NoCmdChange = 0x07, // no command change, can be used to modify the + // CommandReg register bits without affecting + // the command, for example, the PowerDown bit + PCD_Receive = 0x08, // activates the receiver circuits + PCD_Transceive = + 0x0C, // transmits data from FIFO buffer to antenna and + // automatically activates the receiver after transmission + PCD_MFAuthent = + 0x0E, // performs the MIFARE standard authentication as a reader + PCD_SoftReset = 0x0F // resets the MFRC522 + }; + + // MFRC522 RxGain[2:0] masks, defines the receiver's signal voltage gain + // factor (on the PCD). Described in 9.3.3.6 / table 98 of the datasheet at + // http://www.nxp.com/documents/data_sheet/MFRC522.pdf + enum PCD_RxGain { + RxGain_18dB = 0x00 << 4, // 000b - 18 dB, minimum + RxGain_23dB = 0x01 << 4, // 001b - 23 dB + RxGain_18dB_2 = + 0x02 << 4, // 010b - 18 dB, it seems 010b is a duplicate for 000b + RxGain_23dB_2 = + 0x03 << 4, // 011b - 23 dB, it seems 011b is a duplicate for 001b + RxGain_33dB = 0x04 << 4, // 100b - 33 dB, average, and typical default + RxGain_38dB = 0x05 << 4, // 101b - 38 dB + RxGain_43dB = 0x06 << 4, // 110b - 43 dB + RxGain_48dB = 0x07 << 4, // 111b - 48 dB, maximum + RxGain_min = + 0x00 << 4, // 000b - 18 dB, minimum, convenience for RxGain_18dB + RxGain_avg = + 0x04 << 4, // 100b - 33 dB, average, convenience for RxGain_33dB + RxGain_max = + 0x07 << 4 // 111b - 48 dB, maximum, convenience for RxGain_48dB + }; + + // Commands sent to the PICC. + enum PICC_Command { + // The commands used by the PCD to manage communication with several + // PICCs (ISO 14443-3, Type A, section 6.4) + PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in + // state IDLE to go to READY and prepare for + // anticollision or selection. 7 bit frame. + PICC_CMD_WUPA = + 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and + // HALT to go to READY(*) and prepare for anticollision or + // selection. 7 bit frame. + PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used + // during anti collision. + PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 + PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 + PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 + PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC + // to go to state HALT. + // The commands used for MIFARE Classic (from + // http://www.nxp.com/documents/data_sheet/MF1S503x.pdf, Section 9) + // Use PCD_MFAuthent to authenticate access to a sector, then use these + // commands to read/write/modify the blocks on the sector. + // The read/write commands can also be used for MIFARE Ultralight. + PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A + PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B + PICC_CMD_MF_READ = + 0x30, // Reads one 16 byte block from the authenticated sector of + // the PICC. Also used for MIFARE Ultralight. + PICC_CMD_MF_WRITE = + 0xA0, // Writes one 16 byte block to the authenticated sector of + // the PICC. Called "COMPATIBILITY WRITE" for MIFARE + // Ultralight. + PICC_CMD_MF_DECREMENT = + 0xC0, // Decrements the contents of a block and stores the result + // in the internal data register. + PICC_CMD_MF_INCREMENT = + 0xC1, // Increments the contents of a block and stores the result + // in the internal data register. + PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the + // internal data register. + PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal + // data register to a block. + // The commands used for MIFARE Ultralight (from + // http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6) + // The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for + // MIFARE Ultralight. + PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC. + }; + + // MIFARE constants that does not fit anywhere else + enum MIFARE_Misc { + MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other + // value than 0xA is NAK. + MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes. + }; + + // PICC types we can detect. Remember to update PICC_GetTypeName() if you + // add more. + enum PICC_Type { + PICC_TYPE_UNKNOWN = 0, + PICC_TYPE_ISO_14443_4 = 1, // PICC compliant with ISO/IEC 14443-4 + PICC_TYPE_ISO_18092 = 2, // PICC compliant with ISO/IEC 18092 (NFC) + PICC_TYPE_MIFARE_MINI = 3, // MIFARE Classic protocol, 320 bytes + PICC_TYPE_MIFARE_1K = 4, // MIFARE Classic protocol, 1KB + PICC_TYPE_MIFARE_4K = 5, // MIFARE Classic protocol, 4KB + PICC_TYPE_MIFARE_UL = 6, // MIFARE Ultralight or Ultralight C + PICC_TYPE_MIFARE_PLUS = 7, // MIFARE Plus + PICC_TYPE_TNP3XXX = 8, // Only mentioned in NXP AN 10833 MIFARE Type + // Identification Procedure + PICC_TYPE_NOT_COMPLETE = 255 // SAK indicates UID is not complete. + }; + + // Return codes from the functions in this class. Remember to update + // GetStatusCodeName() if you add more. + enum StatusCode { + STATUS_OK = 1, // Success + STATUS_ERROR = 2, // Error in communication + STATUS_COLLISION = 3, // Collission detected + STATUS_TIMEOUT = 4, // Timeout in communication. + STATUS_NO_ROOM = 5, // A buffer is not big enough. + STATUS_INTERNAL_ERROR = + 6, // Internal error in the code. Should not happen ;-) + STATUS_INVALID = 7, // Invalid argument. + STATUS_CRC_WRONG = 8, // The CRC_A does not match + STATUS_MIFARE_NACK = 9 // A MIFARE PICC responded with NAK. + }; + + // A struct used for passing the UID of a PICC. + typedef struct { + byte size; // Number of bytes in the UID. 4, 7 or 10. + byte uidByte[10]; + byte sak; // The SAK (Select acknowledge) byte returned from the PICC + // after successful selection. + } Uid; + + // A struct used for passing a MIFARE Crypto1 key + typedef struct { + byte keyByte[MF_KEY_SIZE]; + } MIFARE_Key; + + // Member variables + Uid uid; // Used by PICC_ReadCardSerial(). + + // Size of the MFRC522 FIFO + static const byte FIFO_SIZE = 64; // The FIFO is 64 bytes. + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for setting up the Arduino + ///////////////////////////////////////////////////////////////////////////////////// + MFRC522(byte chipAddress); + + ///////////////////////////////////////////////////////////////////////////////////// + // Basic interface functions for communicating with the MFRC522 + ///////////////////////////////////////////////////////////////////////////////////// + void PCD_WriteRegister(byte reg, byte value); + void PCD_WriteRegister(byte reg, byte count, byte *values); + byte PCD_ReadRegister(byte reg); + void PCD_ReadRegister(byte reg, byte count, byte *values, byte rxAlign = 0); + void setBitMask(unsigned char reg, unsigned char mask); + void PCD_SetRegisterBitMask(byte reg, byte mask); + void PCD_ClearRegisterBitMask(byte reg, byte mask); + byte PCD_CalculateCRC(byte *data, byte length, byte *result); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for manipulating the MFRC522 + ///////////////////////////////////////////////////////////////////////////////////// + void PCD_Init(); + void PCD_Reset(); + void PCD_AntennaOn(); + void PCD_AntennaOff(); + byte PCD_GetAntennaGain(); + void PCD_SetAntennaGain(byte mask); + bool PCD_PerformSelfTest(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with PICCs + ///////////////////////////////////////////////////////////////////////////////////// + byte PCD_TransceiveData(byte *sendData, byte sendLen, byte *backData, + byte *backLen, byte *validBits = NULL, + byte rxAlign = 0, bool checkCRC = false); + byte PCD_CommunicateWithPICC(byte command, byte waitIRq, byte *sendData, + byte sendLen, byte *backData = NULL, + byte *backLen = NULL, byte *validBits = NULL, + byte rxAlign = 0, bool checkCRC = false); + byte PICC_RequestA(byte *bufferATQA, byte *bufferSize); + byte PICC_WakeupA(byte *bufferATQA, byte *bufferSize); + byte PICC_REQA_or_WUPA(byte command, byte *bufferATQA, byte *bufferSize); + byte PICC_Select(Uid *uid, byte validBits = 0); + byte PICC_HaltA(); + + ///////////////////////////////////////////////////////////////////////////////////// + // Functions for communicating with MIFARE PICCs + ///////////////////////////////////////////////////////////////////////////////////// + byte PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, + Uid *uid); + void PCD_StopCrypto1(); + byte MIFARE_Read(byte blockAddr, byte *buffer, byte *bufferSize); + byte MIFARE_Write(byte blockAddr, byte *buffer, byte bufferSize); + byte MIFARE_Decrement(byte blockAddr, long delta); + byte MIFARE_Increment(byte blockAddr, long delta); + byte MIFARE_Restore(byte blockAddr); + byte MIFARE_Transfer(byte blockAddr); + byte MIFARE_Ultralight_Write(byte page, byte *buffer, byte bufferSize); + byte MIFARE_GetValue(byte blockAddr, long *value); + byte MIFARE_SetValue(byte blockAddr, long value); + + ///////////////////////////////////////////////////////////////////////////////////// + // Support functions + ///////////////////////////////////////////////////////////////////////////////////// + byte PCD_MIFARE_Transceive(byte *sendData, byte sendLen, + bool acceptTimeout = false); + // old function used too much memory, now name moved to flash; if you need + // char, copy from flash to memory + // const char *GetStatusCodeName(byte code); + const __FlashStringHelper *GetStatusCodeName(byte code); + byte PICC_GetType(byte sak); + // old function used too much memory, now name moved to flash; if you need + // char, copy from flash to memory + // const char *PICC_GetTypeName(byte type); + const __FlashStringHelper *PICC_GetTypeName(byte type); + void PICC_DumpToSerial(Uid *uid); + void PICC_DumpMifareClassicToSerial(Uid *uid, byte piccType, + MIFARE_Key *key); + void PICC_DumpMifareClassicSectorToSerial(Uid *uid, MIFARE_Key *key, + byte sector); + void PICC_DumpMifareUltralightToSerial(); + void MIFARE_SetAccessBits(byte *accessBitBuffer, byte g0, byte g1, byte g2, + byte g3); + bool MIFARE_OpenUidBackdoor(bool logErrors); + bool MIFARE_SetUid(byte *newUid, byte uidSize, bool logErrors); + bool MIFARE_UnbrickUidSector(bool logErrors); + + ///////////////////////////////////////////////////////////////////////////////////// + // Convenience functions - does not add extra functionality + ///////////////////////////////////////////////////////////////////////////////////// + bool PICC_IsNewCardPresent(); + bool PICC_ReadCardSerial(); + + private: + byte _chipAddress; + byte _resetPowerDownPin; // Arduino pin connected to MFRC522's reset and + // power down input (Pin 6, NRSTPD, active low) + byte MIFARE_TwoStepHelper(byte command, byte blockAddr, long data); +}; + +#endif \ No newline at end of file diff --git a/lib/NFC/src/NfcReader.cpp b/lib/NFC/src/NfcReader.cpp new file mode 100644 index 0000000..3fc9085 --- /dev/null +++ b/lib/NFC/src/NfcReader.cpp @@ -0,0 +1,32 @@ +#include "NfcReader.h" + +NfcReader::NfcReader(int i2c_adress) +{ + this->mfrc522 = new MFRC522(i2c_adress); + this->mfrc522->PCD_Init(); +} + +String NfcReader::ReadNfc() +{ + this->uid.clear(); + if (!this->mfrc522->PICC_IsNewCardPresent() || + !this->mfrc522->PICC_ReadCardSerial()) { + return "0"; + } + for (unsigned int i = 0; i < this->mfrc522->uid.size; i++) { + if (this->mfrc522->uid.uidByte[i] < 0xF) { + this->uid += '0'; + this->uid += String(this->mfrc522->uid.uidByte[i], HEX); + } else { + this->uid += String(this->mfrc522->uid.uidByte[i], HEX); + } + } + return (this->uid); +} + +bool NfcReader::IsNfcConnected() +{ + Wire.beginTransmission(NFC_ADDR); + byte error = Wire.endTransmission(); + return error == 0; +} \ No newline at end of file diff --git a/lib/NFC/src/NfcReader.h b/lib/NFC/src/NfcReader.h new file mode 100644 index 0000000..7b990aa --- /dev/null +++ b/lib/NFC/src/NfcReader.h @@ -0,0 +1,22 @@ +#ifndef NFCREADER_H +#define NFCREADER_H + +#include +#include "MFRC522_I2C.h" +#include + +class NfcReader { + public: + NfcReader(int i2c_adress); + ~NfcReader() = default; + bool IsNfcConnected(); + + String ReadNfc(); + + protected: + private: + MFRC522 *mfrc522; + String uid; +}; + +#endif /* !NFCREADER_H */ diff --git a/lib/NfcReader/include/BigNfcReader.h b/lib/NfcReader/include/BigNfcReader.h new file mode 100644 index 0000000..287ecee --- /dev/null +++ b/lib/NfcReader/include/BigNfcReader.h @@ -0,0 +1,67 @@ +#ifndef BIG_NFC_READER_H +#define BIG_NFC_READER_H + +#include +#include + +#include "TrameList.h" + +class BigNfcReader{ +public: + BigNfcReader(); + + /** + * @brief initialise le lecteur NFC + * + * @return true le lecteur NFC est initialisé + * @return false le lecteur NFC n'est pas initialisé (erreur) + */ + bool init(); + + /** + * @brief rafraichit les données du lecteur NFC + * + */ + void refresh(); + + /** + * @brief affiche la dernière trame lue + * + */ + void printTrame(); + + /** + * @brief retourne le nombre de tags lus + * + * @return int nombre de tags lus + */ + int getNbTags(); + +private: + + /** + * @brief convertit un nombre en hexadécimal (a 2 chiffres) + * + * @param number nombre à convertir + * @return String nombre converti + */ + String digitify(int number); + + /** + * @brief variable pair/impair pour le type de trame + * + */ + bool pair; + + /** + * @brief dernière trame lue + * + */ + std::vector* trame; + +}; + + + + +#endif diff --git a/lib/NfcReader/include/TrameList.h b/lib/NfcReader/include/TrameList.h new file mode 100644 index 0000000..9e412ae --- /dev/null +++ b/lib/NfcReader/include/TrameList.h @@ -0,0 +1,29 @@ +#ifndef TRAM_LIST_H +#define TRAM_LIST_H + +#include +#include + +const std::vector SEARCH_TRAM_0 = {0x05, 0x07, 0x01, 0x01, 0x01, 0x00, 0xEB, 0x5F}; + +const std::vector INIT_TRAM_0 = {0xFA, 0x05, 0x01, 0xC0, 0x5A, 0xDF}; +const std::vector INIT_TRAM_1 = {0xFA, 0x08, 0x01, 0x00, 0x01, 0x01, 0x01, 0xE1, 0x70}; +const std::vector INIT_TRAM_2 = {0xFA, 0x08, 0x01, 0x40, 0x01, 0x02, 0x01, 0x3E, 0x4C}; +const std::vector INIT_TRAM_3 = {0xFA, 0x08, 0x01, 0x00, 0x01, 0x03, 0x01, 0x51, 0x43}; +const std::vector INIT_TRAM_4 = {0xFA, 0x08, 0x01, 0x40, 0x01, 0x04, 0x01, 0xEE, 0x18}; +const std::vector INIT_TRAM_5 = {0xFA, 0x08, 0x01, 0x00, 0x01, 0x05, 0x01, 0x81, 0x17}; +const std::vector INIT_TRAM_6 = {0xFA, 0x08, 0x01, 0x40, 0x01, 0x06, 0x01, 0x5E, 0x2B}; +const std::vector INIT_TRAM_7 = {0xFA, 0x08, 0x01, 0x00, 0x01, 0x07, 0x01, 0x31, 0x24}; + +const std::vector REP_TRAM_0 = {0xFA, 0x05, 0x01, 0xE0, 0x58, 0xFE}; +const std::vector REP_TRAM_1 = {0xFA, 0x17, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x04, 0x00, 0x00, 0x03, 0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x8A}; +const std::vector REP_TRAM_2 = {0xFA, 0x17, 0x01, 0x40, 0x01, 0x00, 0x00, 0x01, 0x00, 0x09, 0x00, 0xFF, 0x00, 0x10, 0x07, 0x13, 0x01, 0x0E, 0x08, 0x09, 0x0B, 0x00, 0x2D, 0x98}; +const std::vector REP_TRAM_3 = {0xFA, 0x17, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x43, 0x83, 0x00, 0x00, 0x00, 0x9A, 0xBF}; +const std::vector REP_TRAM_4 = {0xFA, 0x17, 0x01, 0x40, 0x01, 0x00, 0x00, 0x01, 0x04, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD7, 0x15}; +const std::vector REP_TRAM_5 = {0xFA, 0x17, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0A, 0x03, 0x0A, 0x00, 0xF6, 0xCD}; +const std::vector REP_TRAM_6 = {0xFA, 0x17, 0x01, 0x40, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xD4, 0x03}; +const std::vector REP_TRAM_7 = {0xFA, 0x17, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xF6}; + +const std::vector READ_TRAM_0 = {0xFA, 0x24, 0x01, 0x00, 0x31, 0x02, 0x07, 0x0E, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x06, 0x00, 0x03, 0x00, 0xFF, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x5D, 0x74}; +const std::vector READ_TRAM_1 = {0xFA, 0x24, 0x01, 0x40, 0x31, 0x02, 0x07, 0x0E, 0x00, 0x00, 0x04, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x06, 0x00, 0x03, 0x00, 0xFF, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x10, 0x45}; +#endif diff --git a/lib/NfcReader/src/BigNfcReader.cpp b/lib/NfcReader/src/BigNfcReader.cpp new file mode 100644 index 0000000..abbbf7c --- /dev/null +++ b/lib/NfcReader/src/BigNfcReader.cpp @@ -0,0 +1,75 @@ +#include "../include/BigNfcReader.h" + + +BigNfcReader::BigNfcReader(){ + Serial2.begin(115200, SERIAL_8N1, 16, 17);//TODO: mettre le port série en paramètre + this->pair = true; + this->trame = new std::vector(); +} + + +bool BigNfcReader::init(){ + const std::vector> INIT_TRAMES = {INIT_TRAM_0, INIT_TRAM_1, INIT_TRAM_2, INIT_TRAM_3, INIT_TRAM_4, INIT_TRAM_5, INIT_TRAM_6, INIT_TRAM_7}; + const std::vector> REP_TRAMES = {REP_TRAM_0, REP_TRAM_1, REP_TRAM_2, REP_TRAM_3, REP_TRAM_4, REP_TRAM_5, REP_TRAM_6, REP_TRAM_7}; + int repnb = 0; + for(std::vector i: INIT_TRAMES){ + for(byte j: i){ + Serial2.write(j); + } + while (Serial2.available() == 0); + while (Serial2.available() > 0){ + for(byte j: REP_TRAMES[repnb]){ + if(Serial2.read() != j){ + Serial.println("NFC reader init failed"); + return false; + } + } + } + repnb++; + } + return true; +} + +void BigNfcReader::refresh(){ + this->trame->clear(); + std::vector trame = READ_TRAM_0; + if (this->pair){ + trame = READ_TRAM_1; + } + this->pair = !this->pair; + + for(byte i: trame){ + Serial2.write(i); + } + while (Serial2.available() == 0); + while (Serial2.available() > 0){ + this->trame->push_back(Serial2.read()); + } +} + + +String BigNfcReader::digitify(int number){ + String sortie = ""; + + if(number <= 0xF){ + sortie += '0'; + sortie += String(number,HEX); + }else{ + sortie += String(number,HEX); + } + + return sortie; +} + + +void BigNfcReader::printTrame(){ + for(byte i: *this->trame){ + Serial.print(this->digitify(i)); + Serial.print(" "); + } + Serial.println(); +} + +int BigNfcReader::getNbTags(){ + return this->trame->at(8); +} diff --git a/lib/ServoMotorComponent/src/ServoMotorComponent.cpp b/lib/ServoMotorComponent/src/ServoMotorComponent.cpp new file mode 100644 index 0000000..2ccc551 --- /dev/null +++ b/lib/ServoMotorComponent/src/ServoMotorComponent.cpp @@ -0,0 +1,63 @@ +#include "ServoMotorComponent.h" + + +///////////////////////////////////////////////////////////////////////////////////// +// Functions for setting up the ServoMotor +///////////////////////////////////////////////////////////////////////////////////// + +/** + * Constructor. + * Prepares the ServoMotor. + */ +ServoMotorComponent::ServoMotorComponent(int PIN, unsigned long updatePeriod, float step) { + this->PIN = PIN; + this->myservo.attach(PIN); + this->desiredposition = Position::MIDDLE; + this->currentPosition = MIDDLE_POS; + this->lastUpTime = millis(); + this->updatePeriod = updatePeriod; + this->step = step; + this->myservo.write(this->PIN, this->currentPosition); +} + +/** + * Set the desired position + * @desiredPosition: Give desired position + */ +void ServoMotorComponent::setDesiredPosition(Position desiredPosition) { + switch (desiredPosition) { + case Position::LEFT: + this->desiredposition = LEFT_POS; + break; + case Position::MIDDLE: + this->desiredposition = MIDDLE_POS; + break; + case Position::RIGHT: + this->desiredposition = RIGHT_POS; + break; + default: + break; + } +} + + +/** + * Write a new servoMotor position when it's necessary + */ +void ServoMotorComponent::refresh() { + if (this->desiredposition == this->currentPosition + || millis() - this->lastUpTime <= this->updatePeriod) return; + + if (this->currentPosition > this->desiredposition) { + this->currentPosition -= this->step; + } + if (this->currentPosition < this->desiredposition) { + this->currentPosition += this->step; + } + this->lastUpTime = millis(); + this->myservo.write(this->PIN, this->currentPosition); +} + +bool ServoMotorComponent::isConnected() { + return true; +} \ No newline at end of file diff --git a/lib/ServoMotorComponent/src/ServoMotorComponent.h b/lib/ServoMotorComponent/src/ServoMotorComponent.h new file mode 100644 index 0000000..0e457a8 --- /dev/null +++ b/lib/ServoMotorComponent/src/ServoMotorComponent.h @@ -0,0 +1,34 @@ +#ifndef SERVOMOTOT_COMPONENT_H +#define SERVOMOTOT_COMPONENT_H + +#include + +enum Position { + LEFT, + MIDDLE, + RIGHT +}; + +class ServoMotorComponent +{ +public: + ServoMotorComponent(int PIN, unsigned long updatePeriod = 100, float step = 0.1); + void setDesiredPosition(Position desiredPosition); + bool isConnected(); + void refresh(); + +private: + int PIN; + float currentPosition; + float desiredposition; + Servo myservo; + unsigned long lastUpTime; + unsigned long updatePeriod; + float step; +}; + + + + + +#endif //SERVOMOTOT_COMPONENT_H diff --git a/lib/WorldTime/src/WorldTime.cpp b/lib/WorldTime/src/WorldTime.cpp new file mode 100644 index 0000000..e8c6c0f --- /dev/null +++ b/lib/WorldTime/src/WorldTime.cpp @@ -0,0 +1 @@ +#include "WorldTime.h" \ No newline at end of file diff --git a/lib/WorldTime/src/WorldTime.h b/lib/WorldTime/src/WorldTime.h new file mode 100644 index 0000000..1a1952f --- /dev/null +++ b/lib/WorldTime/src/WorldTime.h @@ -0,0 +1,6 @@ +#ifndef T_IOT_901_CONVOYOR_WORLDTIME_H +#define T_IOT_901_CONVOYOR_WORLDTIME_H + + + +#endif //T_IOT_901_CONVOYOR_WORLDTIME_H \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 6efbe50..f926a91 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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,11 @@ 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) + dlloydev/ESP32 ESP32S2 AnalogWrite ; Lib pour le Servo Motor ; 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..0067723 100644 --- a/src/Program.cpp +++ b/src/Program.cpp @@ -1,10 +1,172 @@ #include "Program.h" +#include "Arduino.h" +#include "M5LCD.h" +#include "DolibarrClient.h" +#include "ServoMotorComponent.h" +#include "NfcReader.h" +#include + +uint32_t derniereExecution = 0; +const uint32_t intervalle = 1000; +std::vector *warehouses; + +int initialize_wifi(WifiConfig wifi) { + lcdScreen->add_log("Connecting to the WiFi network..."); + WiFiClass::mode(WIFI_STA); //Optional + WiFi.setSleep(false); + WiFi.begin(wifi.ssid, wifi.password); + return 0; +} + +void Program::checkNfc() { + if (this->nfcReader->IsNfcConnected() && lcdScreen->get_components().nfc != COMPONENT_OK) { + lcdScreen->set_nfc_status(COMPONENT_OK); + lcdScreen->add_log("NFC component connected !"); + Serial.println("NFC IS CONNECTED !"); + } else if (!this->nfcReader->IsNfcConnected() && lcdScreen->get_components().nfc == COMPONENT_OK) { + lcdScreen->set_nfc_status(COMPONENT_KO); + lcdScreen->add_log("NFC component disconnected !"); + Serial.println("NFC NOT CONNECTED !"); + } +} + +void Program::checkServo() { + if (this->servo->isConnected() && lcdScreen->get_components().servo != COMPONENT_OK) { + lcdScreen->set_servo(COMPONENT_OK); + lcdScreen->add_log("SERVO component connected !"); + Serial.println("SERVO IS CONNECTED !"); + } else if (!this->servo->isConnected() && lcdScreen->get_components().servo == COMPONENT_OK) { + lcdScreen->set_servo(COMPONENT_KO); + lcdScreen->add_log("SERVO component disconnected !"); + Serial.println("SERVO NOT CONNECTED !"); + } +} + +void Program::checkWifi() { + if (WiFiClass::status() == WL_CONNECTED && lcdScreen->get_components().wifi != COMPONENT_OK) { + lcdScreen->add_log("Connected to the WiFi network"); + lcdScreen->set_wifi_status(COMPONENT_OK); + struct DolibarrConfig dolibarr = {DOLIBARR_URL, DOLIBARR_API_TOKEN}; + this->client = new DolibarrClient(dolibarr); + warehouses = this->client->list_warehouse(); + if (warehouses != nullptr) { + lcdScreen->add_log("Warehouses found !"); + lcdScreen->set_dolibarr_status(COMPONENT_OK); + for (auto &ware : *warehouses) { + char buffer[50]; + sprintf(buffer, "+ Warehouse '%s' (%s)", ware.label.c_str(), ware.id.c_str()); + lcdScreen->add_log(buffer); + } + } else { + lcdScreen->add_log("Warehouse not found"); + lcdScreen->set_dolibarr_status(COMPONENT_KO); + } + } else if (WiFiClass::status() != WL_CONNECTED && lcdScreen->get_components().wifi == COMPONENT_OK) { + lcdScreen->add_log("Wifi signal lost, reconnecting..."); + lcdScreen->set_wifi_status(COMPONENT_KO); + lcdScreen->set_dolibarr_status(COMPONENT_KO); + struct WifiConfig wifi_c = {WIFI_SSID, WIFI_PASSWORD}; + initialize_wifi(wifi_c); + } +} Program::Program() { - // Startup Serial.begin(MONITOR_SPEED); + Wire.begin(); + delay(1000); + lcdScreen = new M5LCD(); + lcdScreen->add_log("Initialize M5LCD component...."); + this->nfcReader = new NfcReader(NFC_ADDR); + this->servo = new ServoMotorComponent(2, 1, 1); + this->servo->setDesiredPosition(Position::MIDDLE); + this->grbl = new GRBL(STEPMOTOR_I2C_ADDR); + this->outputReader = new BigNfcReader(); + this->outputReader->init(); + this->grblUpdateTime = 0; + Wire.begin(21, 22); + grbl->init(STEPER_SPEED, STEPER_PAS, STEPER_ACC); + struct WifiConfig wifi_c = {WIFI_SSID, WIFI_PASSWORD}; + initialize_wifi(wifi_c); } void Program::loop() { - // Loop + lcdScreen->update(); + + uint32_t maintenant = millis(); + if (maintenant - derniereExecution >= intervalle) { + this->checkServo(); + this->checkNfc(); + this->checkWifi(); + derniereExecution = maintenant; + } + this->servo->refresh(); + this->outputReader->refresh(); + // Serial.println(this->outputReader->getNbTags()); + + if(this->outputReader->getNbTags() >= 2){ + lcdScreen->set_nfc_message("To mutch colis number detected"); + return; + } + + String nfcId = this->nfcReader->ReadNfc(); + //si qqc + if(nfcId != "0"){ + if (lcdScreen->get_components().wifi != COMPONENT_OK) { + lcdScreen->add_log("Wifi not connected !"); + lcdScreen->set_nfc_message("Cannot send wifi request !"); + return; + } + //j'arrète le stepper + //this->grbl->stop(); //FIXME: implemente + //j'affiche le tag lue + Serial.print("new colis in comming : "); + lcdScreen->add_log("new colis detected: "); + lcdScreen->add_log(nfcId.c_str()); + lcdScreen->set_nfc_message(nfcId.c_str()); + auto product = this->client->get_product_by_factory_id(nfcId.c_str()); + Serial.printf("Product: %s\n%s", product.label.c_str(), product.accountancy_code_sell_export.c_str()); + lcdScreen->set_product_label(product.label); + lcdScreen->set_product_id(product.id); + /*if (product.fk_default_warehouse == product.accountancy_code_sell_export) { + Serial.printf("Product already in the good warehouse !\n"); + return; + }*/ + auto ware = std::find_if(warehouses->begin(), warehouses->end(), [&product](models::Warehouse w) {return w.id == product.accountancy_code_sell_export;}); + if (ware.base() == nullptr) { + Serial.printf("Warehouse not found !\n"); + return; + } + Serial.printf("Product need to go to warehouse %s\n", ware->label.c_str()); + this->client->create_movement(models::CreateProductStock{ + product.id, + product.fk_default_warehouse, + "-1" + }); + if (this->client->create_movement(models::CreateProductStock{ + product.id, + ware->id, + "1" + }) == 0) { + Serial.printf("Movement created !\n"); + } else { + Serial.printf("Movement cannot be created !\n"); + } + if (ware->id == "1") { + this->servo->setDesiredPosition(Position::RIGHT); + lcdScreen->set_servo_message("RIGHT"); + } else if (ware->id == "2") { + this->servo->setDesiredPosition(Position::LEFT); + lcdScreen->set_servo_message("LEFT"); + } else if (ware->id == "3") { + this->servo->setDesiredPosition(Position::MIDDLE); + lcdScreen->set_servo_message("MIDDLE"); + } + this->grbl->mouveForward(CONVOYER_LEN); + } else { + if(this->grbl->isIddle() || (maintenant - this->grblUpdateTime >= GRBL_UPDATE)){ + this->grblUpdateTime = maintenant; + this->grbl->mouveForward(5); + Serial.print("pouet"); + } + } } diff --git a/src/main.cpp b/src/main.cpp index 74bf5a7..bf9fbf6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include "Program.h" - Program* program; void setup() { 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