diff --git a/Formation Web.postman_collection.json b/CHAPITRE1/Formation Web.postman_collection.json similarity index 100% rename from Formation Web.postman_collection.json rename to CHAPITRE1/Formation Web.postman_collection.json diff --git a/README.md b/CHAPITRE1/README.md similarity index 100% rename from README.md rename to CHAPITRE1/README.md diff --git a/CHAPITRE1/desktop.ini b/CHAPITRE1/desktop.ini new file mode 100644 index 0000000000000000000000000000000000000000..cc4e0f9578910ec74598927d5b674872d6f2defb --- /dev/null +++ b/CHAPITRE1/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=C:\Program Files\Google\Drive File Stream\70.0.2.0\GoogleDriveFS.exe,23 diff --git "a/CHAPITRE2/corrig\303\251/ChestsTrigonometryHelper.cpp" "b/CHAPITRE2/corrig\303\251/ChestsTrigonometryHelper.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..f0ea50335d96f89c8e662ec15ccc543eed76e6e3 --- /dev/null +++ "b/CHAPITRE2/corrig\303\251/ChestsTrigonometryHelper.cpp" @@ -0,0 +1,24 @@ +#include "ChestsTrigonometryHelper.h" + +double getHorizontalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cx = requiredChests[requiredChestIdx]["location"]["x"].as<double>(); + double cz = requiredChests[requiredChestIdx]["location"]["z"].as<double>(); + return sqrt(pow(cx-px, 2) + pow(cz-pz, 2)); +} + +double getVerticalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double py) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cy = requiredChests[requiredChestIdx]["location"]["y"].as<double>(); + return abs(cy-py); +} + +double getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cx = requiredChests[requiredChestIdx]["location"]["x"].as<double>(); + double cz = requiredChests[requiredChestIdx]["location"]["z"].as<double>(); + double angle = pRot - (atan2(px-cx, cz-pz)*180.0/3.1415); + if (angle <= -180.0) angle+=360.0; + else if (angle >= 180.0) angle-=360.0; + return angle; +} \ No newline at end of file diff --git "a/CHAPITRE2/corrig\303\251/ChestsTrigonometryHelper.h" "b/CHAPITRE2/corrig\303\251/ChestsTrigonometryHelper.h" new file mode 100644 index 0000000000000000000000000000000000000000..252a4c2458177b1d0ba0f7f52a058c69a03676e5 --- /dev/null +++ "b/CHAPITRE2/corrig\303\251/ChestsTrigonometryHelper.h" @@ -0,0 +1,13 @@ +#ifndef CHESTSTRIGONOMETRYHELPER_H_ +#define CHESTSTRIGONOMETRYHELPER_H_ + +#include <ArduinoJson.h> + +double getHorizontalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz); + +double getVerticalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double py); + +double getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot); + + +#endif \ No newline at end of file diff --git "a/CHAPITRE2/corrig\303\251/LedMatrixHelper.cpp" "b/CHAPITRE2/corrig\303\251/LedMatrixHelper.cpp" new file mode 100644 index 0000000000000000000000000000000000000000..f8cf7d67a3828d163025e02f30403608841ec8d2 --- /dev/null +++ "b/CHAPITRE2/corrig\303\251/LedMatrixHelper.cpp" @@ -0,0 +1,17 @@ +#include "LedMatrixHelper.h" + +MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); + +void initLedMatrix() { + mx.begin(); + mx.control(MD_MAX72XX::INTENSITY, 0); + mx.clear(); +} + +void drawMatrix(int matrix[8][8], int filter) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + mx.setPoint(i, j, matrix[i][j] == filter); + } + } +} diff --git "a/CHAPITRE2/corrig\303\251/LedMatrixHelper.h" "b/CHAPITRE2/corrig\303\251/LedMatrixHelper.h" new file mode 100644 index 0000000000000000000000000000000000000000..7e14b22ddf7e3fb32ef2ca2de26187a27fd72742 --- /dev/null +++ "b/CHAPITRE2/corrig\303\251/LedMatrixHelper.h" @@ -0,0 +1,16 @@ +#ifndef LEDMATRIXHELPER_H_ +#define LEDMATRIXHELPER_H_ + +#include <MD_MAX72xx.h> +#include <SPI.h> + +#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW +#define MAX_DEVICES 1 +#define CS_PIN 15 + +void initLedMatrix(); + +void drawMatrix(int matrix[8][8], int filter); + + +#endif \ No newline at end of file diff --git "a/CHAPITRE2/corrig\303\251/desktop.ini" "b/CHAPITRE2/corrig\303\251/desktop.ini" new file mode 100644 index 0000000000000000000000000000000000000000..cc4e0f9578910ec74598927d5b674872d6f2defb --- /dev/null +++ "b/CHAPITRE2/corrig\303\251/desktop.ini" @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=C:\Program Files\Google\Drive File Stream\70.0.2.0\GoogleDriveFS.exe,23 diff --git "a/CHAPITRE2/corrig\303\251/esp_src_formation_web.ino" "b/CHAPITRE2/corrig\303\251/esp_src_formation_web.ino" new file mode 100644 index 0000000000000000000000000000000000000000..9d393c4d53480da252021c7765edbaba5e729e1d --- /dev/null +++ "b/CHAPITRE2/corrig\303\251/esp_src_formation_web.ino" @@ -0,0 +1,211 @@ +#include "LedMatrixHelper.h" +#include "ChestsTrigonometryHelper.h" + +#include <ESP8266WiFi.h> +#include <ESP8266HTTPClient.h> +#include <WiFiUdp.h> +#include <PubSubClient.h> +#include <ArduinoJson.h> +#include <string.h> +#include <Servo.h> + +Servo compassServo; + + +// ####### 1.a. CONFIGURATION ET DECLARATION DU HOSTPOT WIFI ####### +char ssid[] = ""; // le nom du reseau WIFI +char password[] = ""; // le mot de passe WIFI +WiFiClient espClient; + +// ####### 1.b. DECLARATION DU CLIENT HTTP ET DES VARIABLES DE STOCKAGE DES DONNÉES DE JEU ####### +HTTPClient http; +int teamPixelArt[8][8]; +StaticJsonDocument<2048> requiredChestsDoc; +JsonArray requiredChests = requiredChestsDoc.to<JsonArray>(); + + +// ####### 1.c. ACQUISITION DU PIXEL ART VIA REQUETE HTTP ET LECTURE DU JSON ####### +void fetchTeamPixelArt() { + http.useHTTP10(true); + http.begin(espClient, "http://192.168.1.17:3000/pixel-arts/yellow"); + http.GET(); + DynamicJsonDocument doc(2048); + deserializeJson(doc, http.getStream()); + JsonArray teamPixelArtArray = doc["pixelArt"].as<JsonArray>(); + for (int i = 0; i < teamPixelArtArray.size(); i++) { + for (int j = 0; j < teamPixelArtArray[0].size(); j++) { + teamPixelArt[i][j] = teamPixelArtArray[i][j].as<int>(); + Serial.print(teamPixelArt[i][j]); + Serial.print(" "); + } + Serial.println(); + } + http.end(); +} + +bool isColorInTeamPixelArt(int color) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + if (teamPixelArt[i][j] == color) return true; + } + } + return false; +} + +// ####### 1.d. ACQUISITION DES COFFRES COMPORTANT LES COULEURS DONT NOUS AVONS BESOIN ####### +void fetchRequiredChests() { + http.useHTTP10(true); + http.begin(espClient, "http://192.168.1.17:3000/chests"); + http.GET(); + DynamicJsonDocument doc(4096); + deserializeJson(doc, http.getStream()); + JsonArray chestArray = doc.as<JsonArray>(); + for (JsonObject chest : chestArray) { + int chestContentColor = chest["content"].as<int>(); + if (isColorInTeamPixelArt(chestContentColor)) { + requiredChests.add(chest); + } + } + for (JsonObject c : requiredChests) { + Serial.print("Coffre à chercher : \""); + Serial.print(c["name"].as<String>()); + Serial.print("\" aux coordonnées: x:"); + Serial.print(c["location"]["x"].as<int>()); + Serial.print(" , y:"); + Serial.print(c["location"]["y"].as<int>()); + Serial.print(" , z:"); + Serial.println(c["location"]["z"].as<int>()); + } +} + +// ####### 2.a. CONFIGURATION ET DECLARATION DU SERVEUR MQTT ####### +char mqtt_server[] = "192.168.1.17"; //adresse IP serveur +#define MQTT_USER "" +#define MQTT_PASS "" +PubSubClient MQTTclient(espClient); + + +//C'est cadeau pour vous éviter de vous taper du parsing à la main en C :p +String getSubTopicAtIndex(String topic, int index) { + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = topic.length()-1; + for(int i=0; i<=maxIndex && found<=index; i++){ + if(topic.charAt(i)=='/' || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + return found>index ? topic.substring(strIndex[0], strIndex[1]) : ""; +} + +void MQTTconnect() { + while (!MQTTclient.connected()) { + Serial.print("Attente de la connexion MQTT...."); + String clientId = "TestClient-"; + clientId += String(random(0xffff), HEX); + + // test connexion + if (MQTTclient.connect(clientId.c_str(),"","")) { + Serial.println("connected"); + + // ####### 2.a. SOUSCRIPTION AUX TOPICS MQTT DE MON/MES JOUEURS ####### + MQTTclient.subscribe("redTeam/AntoineKia/#"); + + } else { // si echec affichage erreur + Serial.print("ECHEC, rc="); + Serial.print(MQTTclient.state()); + Serial.println(" nouvelle tentative dans 5 secondes"); + delay(5000); + } + } +} + +void MQTTcallback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message MQTT reçu sur ["); + String topicString = String((char*)topic); + Serial.print(topicString); + Serial.println("] "); + payload[length] = '\0'; + + // ####### 2.b. "AIGUILLAGE" DES MESSAGES MQTT SELON LES TOPICS ####### + // Vous pouvez utiliser la fonction getSubTopicAtIndex(topicString, VOTRE_INDEX) + // Par exemple getSubTopicAtIndex(topicString, 1) retournera "AntoineRcbs" + // pour l'entrée topicString = "redTeam/AntoineRcbs/location" + if (getSubTopicAtIndex(topicString, 2) == "location") { + onPlayerLocationUpdate(payload, length); + } else if (getSubTopicAtIndex(topicString, 2) == "last-selected-construction-item") { + onConstructionItemHeld(payload, length); + } +} + + +// ####### 2.c. Décodage de la donnée (numéro du block de construction tenu) et mise à jour de l'affichage ####### +// On pourra prendre un DynamicJsonDocument de taille 128 +void onConstructionItemHeld(byte* payload, unsigned int length) { + int itemId = atoi((char*)payload); + Serial.print("Construction item hold (id) : "); + Serial.println(itemId); + drawMatrix(teamPixelArt, itemId); +} + + + +// ####### 2.c. Décodage du JSON et traitement sur les données extraites ####### +// On pourra prendre un DynamicJsonDocument de taille 128 +void onPlayerLocationUpdate(byte* payload, unsigned int length) { + DynamicJsonDocument playerLocation(128); + deserializeJson(playerLocation, payload, length); + double x, y, z, rot; + x = playerLocation["x"]; + y = playerLocation["y"]; + z = playerLocation["z"]; + rot = playerLocation["rotation"]; + Serial.print("Distance horizontale au coffre : "); + Serial.print(getHorizontalDistanceToRequiredChest(requiredChests, 0, x, z)); + Serial.print(", distance verticale : "); + Serial.print(getVerticalDistanceToRequiredChest(requiredChests, 0, y)); + Serial.print(", direction :"); + double angle = getAngleDirectionDifferenceToRequiredChest(requiredChests, 0, x, z, rot); + Serial.println(angle); + int servoAngle = max(-90, min(90, (int) angle)) + 90; + compassServo.write(servoAngle); +} + + + +void setup() { + initLedMatrix(); + compassServo.attach(D4, 300, 2700); + Serial.begin(115200); + + // ####### 1.a. CONNEXION AU WIFI ####### + WiFi.begin(ssid, password); + Serial.println(""); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("Connected to WIFI"); + + // ######## 1.c. REQUETE HTTP POUR PIXEL ART ######## + fetchTeamPixelArt(); + + // ######## 1.d. REQUETE HTTP POUR COFFRES ######## + fetchRequiredChests(); + + + // ####### 2.a. INITIALISATION DU SERVEUR ET DU CALLBACK MQTT ####### + MQTTclient.setServer(mqtt_server, 1883); + MQTTclient.setCallback(MQTTcallback); +} + +void loop() { + + // ####### 2.a. RECONNEXION ET POOLING LOOP MQTT ####### + if (!MQTTclient.connected()) { + MQTTconnect(); + } + MQTTclient.loop(); +} \ No newline at end of file diff --git a/CHAPITRE2/desktop.ini b/CHAPITRE2/desktop.ini new file mode 100644 index 0000000000000000000000000000000000000000..cc4e0f9578910ec74598927d5b674872d6f2defb --- /dev/null +++ b/CHAPITRE2/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=C:\Program Files\Google\Drive File Stream\70.0.2.0\GoogleDriveFS.exe,23 diff --git a/CHAPITRE2/formation_web_esp/ChestsTrigonometryHelper.cpp b/CHAPITRE2/formation_web_esp/ChestsTrigonometryHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f0ea50335d96f89c8e662ec15ccc543eed76e6e3 --- /dev/null +++ b/CHAPITRE2/formation_web_esp/ChestsTrigonometryHelper.cpp @@ -0,0 +1,24 @@ +#include "ChestsTrigonometryHelper.h" + +double getHorizontalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cx = requiredChests[requiredChestIdx]["location"]["x"].as<double>(); + double cz = requiredChests[requiredChestIdx]["location"]["z"].as<double>(); + return sqrt(pow(cx-px, 2) + pow(cz-pz, 2)); +} + +double getVerticalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double py) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cy = requiredChests[requiredChestIdx]["location"]["y"].as<double>(); + return abs(cy-py); +} + +double getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cx = requiredChests[requiredChestIdx]["location"]["x"].as<double>(); + double cz = requiredChests[requiredChestIdx]["location"]["z"].as<double>(); + double angle = pRot - (atan2(px-cx, cz-pz)*180.0/3.1415); + if (angle <= -180.0) angle+=360.0; + else if (angle >= 180.0) angle-=360.0; + return angle; +} \ No newline at end of file diff --git a/CHAPITRE2/formation_web_esp/ChestsTrigonometryHelper.h b/CHAPITRE2/formation_web_esp/ChestsTrigonometryHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..252a4c2458177b1d0ba0f7f52a058c69a03676e5 --- /dev/null +++ b/CHAPITRE2/formation_web_esp/ChestsTrigonometryHelper.h @@ -0,0 +1,13 @@ +#ifndef CHESTSTRIGONOMETRYHELPER_H_ +#define CHESTSTRIGONOMETRYHELPER_H_ + +#include <ArduinoJson.h> + +double getHorizontalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz); + +double getVerticalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double py); + +double getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot); + + +#endif \ No newline at end of file diff --git a/CHAPITRE2/formation_web_esp/LedMatrixHelper.cpp b/CHAPITRE2/formation_web_esp/LedMatrixHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f8cf7d67a3828d163025e02f30403608841ec8d2 --- /dev/null +++ b/CHAPITRE2/formation_web_esp/LedMatrixHelper.cpp @@ -0,0 +1,17 @@ +#include "LedMatrixHelper.h" + +MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); + +void initLedMatrix() { + mx.begin(); + mx.control(MD_MAX72XX::INTENSITY, 0); + mx.clear(); +} + +void drawMatrix(int matrix[8][8], int filter) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + mx.setPoint(i, j, matrix[i][j] == filter); + } + } +} diff --git a/CHAPITRE2/formation_web_esp/LedMatrixHelper.h b/CHAPITRE2/formation_web_esp/LedMatrixHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..7e14b22ddf7e3fb32ef2ca2de26187a27fd72742 --- /dev/null +++ b/CHAPITRE2/formation_web_esp/LedMatrixHelper.h @@ -0,0 +1,16 @@ +#ifndef LEDMATRIXHELPER_H_ +#define LEDMATRIXHELPER_H_ + +#include <MD_MAX72xx.h> +#include <SPI.h> + +#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW +#define MAX_DEVICES 1 +#define CS_PIN 15 + +void initLedMatrix(); + +void drawMatrix(int matrix[8][8], int filter); + + +#endif \ No newline at end of file diff --git a/CHAPITRE2/formation_web_esp/desktop.ini b/CHAPITRE2/formation_web_esp/desktop.ini new file mode 100644 index 0000000000000000000000000000000000000000..cc4e0f9578910ec74598927d5b674872d6f2defb --- /dev/null +++ b/CHAPITRE2/formation_web_esp/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=C:\Program Files\Google\Drive File Stream\70.0.2.0\GoogleDriveFS.exe,23 diff --git a/CHAPITRE2/formation_web_esp/formation_web_esp.ino b/CHAPITRE2/formation_web_esp/formation_web_esp.ino new file mode 100644 index 0000000000000000000000000000000000000000..923e1202af9025e85254e8d0c24751f687d3adfd --- /dev/null +++ b/CHAPITRE2/formation_web_esp/formation_web_esp.ino @@ -0,0 +1,149 @@ +#include "LedMatrixHelper.h" +#include "ChestsTrigonometryHelper.h" + +#include <ESP8266WiFi.h> +#include <ESP8266HTTPClient.h> +#include <WiFiUdp.h> +#include <PubSubClient.h> +#include <ArduinoJson.h> +#include <string.h> +#include <Servo.h> + +//C'est cadeau pour vous éviter de vous taper du parsing à la main en C :p +String getSubTopicAtIndex(String topic, int index) { + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = topic.length()-1; + for(int i=0; i<=maxIndex && found<=index; i++){ + if(topic.charAt(i)=='/' || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + return found>index ? topic.substring(strIndex[0], strIndex[1]) : ""; +} + +Servo compassServo; + + +// ####### 1.a. CONFIGURATION ET DECLARATION DU HOSTPOT WIFI ####### +char ssid[] = "NOM DU RESEAU"; // le nom du reseau WIFI +char password[] = "MOT DE PASSE"; // le mot de passe WIFI +WiFiClient espClient; + +// ####### 1.b. DECLARATION DU CLIENT HTTP ET DES VARIABLES DE STOCKAGE DES DONNÉES DE JEU ####### +HTTPClient http; +int teamPixelArt[8][8]; +StaticJsonDocument<2048> requiredChestsDoc; +JsonArray requiredChests = requiredChestsDoc.to<JsonArray>(); + +// ####### 2.a. CONFIGURATION ET DECLARATION DU SERVEUR MQTT ####### +char mqtt_server[] = "IP OU ADRESSE DU SERVEUR"; //adresse IP serveur +#define MQTT_USER "" +#define MQTT_PASS "" +PubSubClient MQTTclient(espClient); + + +// ####### 1.c. ACQUISITION DU PIXEL ART VIA REQUETE HTTP ET LECTURE DU JSON ####### +void fetchTeamPixelArt() { + /* TODO */ +} + +// Fonction simple mais si ça peut vous permettre de gagner 2 minutes :) +bool isColorInTeamPixelArt(int color) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + if (teamPixelArt[i][j] == color) return true; + } + } + return false; +} + +// ####### 1.d. ACQUISITION DES COFFRES COMPORTANT LES COULEURS DONT NOUS AVONS BESOIN ####### +void fetchRequiredChests() { + /* TODO */ +} + + + + + + +void MQTTconnect() { + while (!MQTTclient.connected()) { + Serial.print("Attente de la connexion MQTT...."); + String clientId = "TestClient-"; + clientId += String(random(0xffff), HEX); + + // test connexion + if (MQTTclient.connect(clientId.c_str(),"","")) { + Serial.println("connected"); + + // ####### 2.b. SOUSCRIPTION AUX TOPICS MQTT DE MON/MES JOUEURS ####### + /* TODO */ + + } else { // si echec affichage erreur + Serial.print("ECHEC, rc="); + Serial.print(MQTTclient.state()); + Serial.println(" nouvelle tentative dans 5 secondes"); + delay(5000); + } + } +} + +void MQTTcallback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message MQTT reçu sur ["); + String topicString = String((char*)topic); + Serial.print(topicString); + Serial.println("] "); + payload[length] = '\0'; + + // ####### 2.b. "AIGUILLAGE" DES MESSAGES MQTT SELON LES TOPICS ####### + // Vous pouvez utiliser la fonction getSubTopicAtIndex(topicString, VOTRE_INDEX) + // Par exemple getSubTopicAtIndex(topicString, 1) retournera "AntoineRcbs" + // pour l'entrée topicString = "redTeam/AntoineRcbs/location" + /* TODO */ +} + + +// ####### 2.c. Décodage de la donnée (numéro du block de construction tenu) et mise à jour de l'affichage ####### +// On pourra prendre un DynamicJsonDocument de taille 128 +void onConstructionItemHeld(byte* payload, unsigned int length) { + /* TODO */ +} + + + +// ####### 2.c. Décodage du JSON et traitement sur les données extraites pour servo (+bonus affichage) ####### +// On pourra prendre un DynamicJsonDocument de taille 128 +void onPlayerLocationUpdate(byte* payload, unsigned int length) { + /* TODO */ +} + + + +void setup() { + initLedMatrix(); + compassServo.attach(D4, 300, 2700); + Serial.begin(115200); + + // ####### 1.a. CONNEXION AU WIFI ####### + /* TODO */ + + // ######## 1.c. REQUETE HTTP POUR PIXEL ART ######## + /* TODO */ + + // ######## 1.d. REQUETE HTTP POUR COFFRES ######## + /* TODO */ + + + // ####### 2.a. INITIALISATION DU SERVEUR ET DU CALLBACK MQTT ####### + /* TODO */ +} + +void loop() { + + // ####### 2.a. RECONNEXION ET POOLING LOOP MQTT ####### + /* TODO */ +} \ No newline at end of file diff --git a/CHAPITRE2/readme.md b/CHAPITRE2/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..914a1b309edfd4ae6c91ac107bfe81f2454559a2 --- /dev/null +++ b/CHAPITRE2/readme.md @@ -0,0 +1,220 @@ +# Formation Web partie 2 : Client HTTP et MQTT sur ESP8266 + +## Prérequis + +### Inclure la famille des ESP dans l'IDE Arduino + +- Aller dans File > Preferences et ajouter ```http://arduino.esp8266.com/stable/package_esp8266com_index.json``` dans le champ "Additional boards manager URLs". + +- Aller dans tools > Boards > Boards Manager puis rechercher esp et installer "esp8266". + +### Bibliothèques à télécharger sur l'IDE Arduino + +Aller dans Sketch > Include Library > Manage Libraries et ajouter les bibliothèques suivantes : + +- MD_Parola +- cloud4rpi-esp-arduino + +### Selection de l'environnement + +- Brancher l'ESP8266 à votre PC +- Dans Tools > boards > esp8266, choisir "NodeMCU 1.0 (ESP-12E Module)" +- Choisir dans Tools > port le port série correspondant à votre ESP. +- Essayez de téléverser le programme pour voir si tout fonctionne bien (compilation et liaison avec l'ESP) +- Ouvrir la console série. Configurée le baudrate à 115200. + +## Mise en place du réseau + +Merci de réserver le réseau Wifi qui sera indiqué par l'encadrant uniquement si vous êtes joueur et pour les ESP8266. Pour les PC de développement, utiliser Eduroam. + +## Duplication du code + +- Ouvrez un terminal à l'emplacement où vous souhaitez travaillez. +- Clonnez ce repertoire git. +- Ouvrez le projet "formation_web_esp" avec l'IDE Arduino. + +Ce code sera notre base par la suite. Il contient la structure avec laquelle on évoluera ainsi que des fonctions que vous pourrez utiliser par la suite. + +## Partie 1 - Faisons une requête HTTP ! + +### A - Connexion au WiFi + +Changer les valeurs ssid et password pour correspondre au Wifi auquel on souhaite se connecter. + +Dans le setup() (fonction exécutée une seule fois lors du démarrage de la carte), renseigner le code permettant d'établir la connexion au Wifi. + +On souhaite attendre tant qu'on est pas connecté. Lors de l'attente on imprimera des "." dans la console (sans sauter de ligne) toutes les 500ms. + +Une fois connecté, on souhaite afficher un message indiquant que la connexion est un succès. +- ```Wifi.begin(ssid, password);``` permet de lancer le processus de connexion au WiFi donné. +- ```WiFi.status() != WL_CONNECTED``` permet de vérifier si on est connecté. +- ```delay(temps_en_ms);``` permet d'attendre un temps donné. +- ```Serial.print("mon message");``` permet d'écrire dans la console. ```Serial.println("mon message");``` fait pareil mais en sautant une ligne à la fin. + +### B - Déclaration du client HTTP et des variables de stockage des données de jeu + +Regarder le code de déclaration de variables correspondant. Voir avec le formateur pour la contextualisation des attendues sur ces variables. + +### C - Faire une requète HTTP pour récupérer le pixel art de votre équipe. + +Commencer par regarder avec un indicateur la "tronche" de la donnée (via le formateur car vous n'aurez pas accès au réseau local avec vos PC de dev). + +Implémenter la fonction fetchTeamPixelArt() pour qu'elle fasse la requête HTTP nécessaire, lise le JSON et extraie dans le tableau d'int teamPixelArt[][] les données qui nous intéressent. + +On adoptera la structure suivante pour la requête HTTP et sa conversion en objet lisible par ArduinoJson. + +```Arduino + http.useHTTP10(true); + http.begin(espClient, "MON ADRESSE HTTP"); + http.GET(); + DynamicJsonDocument doc(2048); + deserializeJson(doc, http.getStream()); + // ICI LE CODE D'EXTRACTION DE DONNÉES + http.end(); +``` + +Le JSON récupéré comporte la donnée qui nous intéresse dans un champs "pixelArt", à sa racine. Ce champ est un tableau JSON. On peut utiliser l'objet JsonArray fourni par ArduinoJson pour travailler avec. +Nous travaillons avec un Stream ici. pour extraire le tableau JSON du pixel art vous pouvez utiliser : +```Arduino +JsonArray teamPixelArtArray = doc["pixelArt"].as<JsonArray>(); +``` +Vous pouvez ensuite parcourir ce tableau 2D comme un tableau classique (via boucle for...). Vous pouvez utiliser ```teamPixelArtArray.size();``` et ```teamPixelArtArray[0].size();``` + +La ligne suivante vous permettra, par exemple, de mettre à jour notre int[][] avec les données issues du tableau JSON qu'on est en train de parcourir. +```Arduino +teamPixelArt[i][j] = teamPixelArtArray[i][j].as<int>(); +``` + +**Profitez du parcours de ce tableau pour afficher les valeurs dans le tableau.** Vous pouvez utiliser ```Serial.print(" ");``` entre chaque case et ```Serial.println(); ``` entre chaque ligne. + +N'oubliez pas d'appeler cette fonction ```fetchTeamPixelArt()``` dans le ```setup()``` à l'endroit indiqué. + +**Vous pouvez maintenant lancer votre code (pas trop loin hihi) et admirer le résultat !** + +### D - Acquisition des données sur les coffres. Filtrage pur ne mémoriser que les coffres utiles + +De même que dans le cas précédent, étudier avec le formateur la "tronche" de la donnée correspondant. + +Mettre en place le code pour faire la requête HTTP et faire la deserialization du JSON. Le document fera une taille de 4096 octets (pour prendre très large...) + +Nous avions instancié un JsonArray pour stocker les coffres qui nous intéressent. Il faut en revanche faire le tri. Le Json que l'on récupère est un tableau. Une fois "converti" au bon format, on peut donc le parcourir ! + +```Arduino +JsonArray chestArray = doc.as<JsonArray>(); + for (JsonObject chest : chestArray) { + //Ajout du coffre au JsonArray requiredChests s'il nous intéressent. + } +``` + +Pour faire le tri, vous pouvez utiliser la fonction ```isColorInTeamPixelArt(int color)``` qu'on vous a gracieusement déjà implémenté. La couleur du matériau contenu dans un coffre est contenu dans le champs "content". +Pour ajouter un coffre au tableau requiredChests, vous pouvez utiliser ```requiredChests.add(chest);```. + +Une fois le tri fait, parcourez votre tableau requiredChests et affichez dans le terminal série les infos qui vous intéressent (les champs "name", "x", "y", "z" et "content"). Vous êtes libre de formatez comme vous voulez (de toute façon c'est vous qui jouez !) + +N'oubliez pas d'appeler cette fonction ```fetchRequiredChests()``` dans le ```setup()``` à l'endroit indiqué. + + +** C'est fini ! Voyez avec l'intructeur pour le déroulement de la première partie ;)** + + +## Partie 2 - Des données en temps réel grâce au protocole MQTT ! + +### A - Connexion au broker MQTT + +Mettez à jour l'adresse du broker MQTT avec celle donnée par le formateur. + +Dans le setup(), rajoutez les lignes suivantes et discutez-en. + +```Arduino +MQTTclient.setServer(mqtt_server, 1883); +MQTTclient.setCallback(MQTTcallback); +``` + +Regardez la fonction ```MQTTconnect()``` déjà implémentée. Discutez-en. +Faites de même avec la fonction ```MQTTCallback``` + +Dans le ```loop()``` (fonction exécutée en boucle infinie juste après le ```setup()```), appellez la fonction ```MQTTconnect()``` si on est déconnecté du broker MQTT. + +On peut vérifier que l'on est connecté au broker via ```MQTTclient.connected()```; + +Juste après cette vérification, ajoutez la ligne suivante dans votre ```loop()```. +```Arduino +MQTTclient.loop(); +``` +Elle permet d'interragir avec le MQTT de manière synchrone. + +**Vous pouvez tester pour voir si la connexion se fait bien.** + + +### B - Souscrivons aux topics et "aiguillons" la donnée pour la traiter ! + +Souscrivez aux topics qui vous intéresent (localisation et item de construction tenu en main de vos joueurs). Vous devez implémenter la souscription dans la fonction ```MQTTConnect()``` à l'emplacemen indiqué. + +La souscription se fait, par exemple, via l'appel suivant : +```Arduino +MQTTclient.subscribe("redTeam/AntoineKia/location"); //Pour un topic précis +// OU +MQTTclient.subscribe("redTeam/AntoineKia/#"); //Pour tous les sous-topics à partir de l'endroit indiqué. +``` + +**Vous pouvez tester et constater que vous recevez bien la donnée.** + +La souscription est faite, il faut désormais traiter l'information. Pour cela, on va mettre à jour le ```MQTTCallback``` (fonction appellée à chaque fois qu'on reçoit une mise à jour sur un topic donné). + +En fonction du topic recu, vous devez appeler soit la fonction ```onConstructionItemHeld(byte* payload, unsigned int length)``` soit ```onPlayerLocationUpdate(byte* payload, unsigned int length)```. +Vous pouvez vous aider de la fonction ```getSubTopicAtIndex(String topicString, int index)``` déjà fournie et implémentée. + +### C - Assister la construction du pixel art via la matrice LED. + +Le topic ```"last-selected-construction-item``` ne contient pas un JSON mais simplement le numéro du dernier matériau de construction tenu en main par un joueur donné. La ligne de code suivante vous permettra de la récupérer sous forme d'int : +```Arduino +int itemId = atoi((char*)payload); +``` +Affichez avec un joli message cette donnée dans la console série. + +Utilisez ensuite l'appel suivant : +```drawMatrix(teamPixelArt, itemId);``` + +Cette fonction, qu'on vous fournit, permet d'allumer les pixels correspondant au matériau donné dans le pixel art donné. Testez de changer d'item dans la main, vous devriez alors voir l'allumage changer. + +### D - Orienter le joueur grace à la boussolle/servo moteur. + +Implementez maintenant la fonction ```onPlayerLocationUpdate(byte* payload, unsigned int length)```. La donnée reçue dans le payload est un JSON. + +Le JSON n'est plus donné sous forme de Stream mais de tableau d'octets. Il s'opère différament. Pour extraire les coordonnées et la direction dans laquelle regarde le joueur, vous pouvez donc utiliser le code suivant. + +```Arduino +DynamicJsonDocument playerLocation(128); +deserializeJson(playerLocation, payload, length); +double x, y, z, rot; +x = playerLocation["x"]; +y = playerLocation["y"]; +z = playerLocation["z"]; +rot = playerLocation["rotation"]; +``` + +**Vous pouvez afficher cette donnée dans la console série pour vérifier que tout va bien.** + +Il faut désormais controler la bousolle pour qu'elle oriente notre joueur. Cela peut se faire via un appel simple : +```Arduino +compassServo.write(servoAngle); +``` +Avec servoAngle un entier entre 0 et 180. + +Regardez la fonction ```getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot)```. Elle permet, pour un JsonArray de coffres donné, un index dans ce tableau donné et pour des coordonnées de joueur donné, d'obtenir l'angle entre la direction dans laquelle le joueur regarde et la direction du coffre. + +On va utiliser cet angle pour orienter notre boussolle/servo. +Cependant, l'angle retourné par cette fonction est entre -180 et 180 degrés. Or le servo ne peut tourner que sur une amplitude de 180 degrés. + +De plus, l'angle donné est un flottant (double), or la fonction qui exploite le servo prend un entier. Il faut donc "caster" l'angle en int, le "tronquer", via les fonctions ```max(a, b)``` et ```min(a, b)```, puis lui ajouter 90; + + +**On dirait bien que vous êtes prêt à jouer en utilisant uniquement l'objet connecté !** + + +## BONUS : + +- Comment changer de coffre ciblé ? Dans le TP, on "hardcodait". Peut-on faire mieux ? Imaginez ! Explorez ! + +- Le ChestsTrigonometryHelper vous donne des fonctions pour avoir la distance de votre joueur à un coffre donné. Vous pouvez vous en servir pour afficher une aide sur la matrice de LED. Vous pouvez vous inspirer des fonctions existantes dans le LedMatrixHelper. + diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 0000000000000000000000000000000000000000..cc4e0f9578910ec74598927d5b674872d6f2defb --- /dev/null +++ b/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=C:\Program Files\Google\Drive File Stream\70.0.2.0\GoogleDriveFS.exe,23 diff --git a/formation_web_esp/ChestsTrigonometryHelper.cpp b/formation_web_esp/ChestsTrigonometryHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f0ea50335d96f89c8e662ec15ccc543eed76e6e3 --- /dev/null +++ b/formation_web_esp/ChestsTrigonometryHelper.cpp @@ -0,0 +1,24 @@ +#include "ChestsTrigonometryHelper.h" + +double getHorizontalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cx = requiredChests[requiredChestIdx]["location"]["x"].as<double>(); + double cz = requiredChests[requiredChestIdx]["location"]["z"].as<double>(); + return sqrt(pow(cx-px, 2) + pow(cz-pz, 2)); +} + +double getVerticalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double py) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cy = requiredChests[requiredChestIdx]["location"]["y"].as<double>(); + return abs(cy-py); +} + +double getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot) { + if (requiredChestIdx >= requiredChests.size()) return -1.0; + double cx = requiredChests[requiredChestIdx]["location"]["x"].as<double>(); + double cz = requiredChests[requiredChestIdx]["location"]["z"].as<double>(); + double angle = pRot - (atan2(px-cx, cz-pz)*180.0/3.1415); + if (angle <= -180.0) angle+=360.0; + else if (angle >= 180.0) angle-=360.0; + return angle; +} \ No newline at end of file diff --git a/formation_web_esp/ChestsTrigonometryHelper.h b/formation_web_esp/ChestsTrigonometryHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..252a4c2458177b1d0ba0f7f52a058c69a03676e5 --- /dev/null +++ b/formation_web_esp/ChestsTrigonometryHelper.h @@ -0,0 +1,13 @@ +#ifndef CHESTSTRIGONOMETRYHELPER_H_ +#define CHESTSTRIGONOMETRYHELPER_H_ + +#include <ArduinoJson.h> + +double getHorizontalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz); + +double getVerticalDistanceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double py); + +double getAngleDirectionDifferenceToRequiredChest(JsonArray requiredChests, int requiredChestIdx, double px, double pz, double pRot); + + +#endif \ No newline at end of file diff --git a/formation_web_esp/LedMatrixHelper.cpp b/formation_web_esp/LedMatrixHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f8cf7d67a3828d163025e02f30403608841ec8d2 --- /dev/null +++ b/formation_web_esp/LedMatrixHelper.cpp @@ -0,0 +1,17 @@ +#include "LedMatrixHelper.h" + +MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); + +void initLedMatrix() { + mx.begin(); + mx.control(MD_MAX72XX::INTENSITY, 0); + mx.clear(); +} + +void drawMatrix(int matrix[8][8], int filter) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + mx.setPoint(i, j, matrix[i][j] == filter); + } + } +} diff --git a/formation_web_esp/LedMatrixHelper.h b/formation_web_esp/LedMatrixHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..7e14b22ddf7e3fb32ef2ca2de26187a27fd72742 --- /dev/null +++ b/formation_web_esp/LedMatrixHelper.h @@ -0,0 +1,16 @@ +#ifndef LEDMATRIXHELPER_H_ +#define LEDMATRIXHELPER_H_ + +#include <MD_MAX72xx.h> +#include <SPI.h> + +#define HARDWARE_TYPE MD_MAX72XX::GENERIC_HW +#define MAX_DEVICES 1 +#define CS_PIN 15 + +void initLedMatrix(); + +void drawMatrix(int matrix[8][8], int filter); + + +#endif \ No newline at end of file diff --git a/formation_web_esp/desktop.ini b/formation_web_esp/desktop.ini new file mode 100644 index 0000000000000000000000000000000000000000..cc4e0f9578910ec74598927d5b674872d6f2defb --- /dev/null +++ b/formation_web_esp/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +IconResource=C:\Program Files\Google\Drive File Stream\70.0.2.0\GoogleDriveFS.exe,23 diff --git a/formation_web_esp/formation_web_esp.ino b/formation_web_esp/formation_web_esp.ino new file mode 100644 index 0000000000000000000000000000000000000000..923e1202af9025e85254e8d0c24751f687d3adfd --- /dev/null +++ b/formation_web_esp/formation_web_esp.ino @@ -0,0 +1,149 @@ +#include "LedMatrixHelper.h" +#include "ChestsTrigonometryHelper.h" + +#include <ESP8266WiFi.h> +#include <ESP8266HTTPClient.h> +#include <WiFiUdp.h> +#include <PubSubClient.h> +#include <ArduinoJson.h> +#include <string.h> +#include <Servo.h> + +//C'est cadeau pour vous éviter de vous taper du parsing à la main en C :p +String getSubTopicAtIndex(String topic, int index) { + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = topic.length()-1; + for(int i=0; i<=maxIndex && found<=index; i++){ + if(topic.charAt(i)=='/' || i==maxIndex){ + found++; + strIndex[0] = strIndex[1]+1; + strIndex[1] = (i == maxIndex) ? i+1 : i; + } + } + return found>index ? topic.substring(strIndex[0], strIndex[1]) : ""; +} + +Servo compassServo; + + +// ####### 1.a. CONFIGURATION ET DECLARATION DU HOSTPOT WIFI ####### +char ssid[] = "NOM DU RESEAU"; // le nom du reseau WIFI +char password[] = "MOT DE PASSE"; // le mot de passe WIFI +WiFiClient espClient; + +// ####### 1.b. DECLARATION DU CLIENT HTTP ET DES VARIABLES DE STOCKAGE DES DONNÉES DE JEU ####### +HTTPClient http; +int teamPixelArt[8][8]; +StaticJsonDocument<2048> requiredChestsDoc; +JsonArray requiredChests = requiredChestsDoc.to<JsonArray>(); + +// ####### 2.a. CONFIGURATION ET DECLARATION DU SERVEUR MQTT ####### +char mqtt_server[] = "IP OU ADRESSE DU SERVEUR"; //adresse IP serveur +#define MQTT_USER "" +#define MQTT_PASS "" +PubSubClient MQTTclient(espClient); + + +// ####### 1.c. ACQUISITION DU PIXEL ART VIA REQUETE HTTP ET LECTURE DU JSON ####### +void fetchTeamPixelArt() { + /* TODO */ +} + +// Fonction simple mais si ça peut vous permettre de gagner 2 minutes :) +bool isColorInTeamPixelArt(int color) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + if (teamPixelArt[i][j] == color) return true; + } + } + return false; +} + +// ####### 1.d. ACQUISITION DES COFFRES COMPORTANT LES COULEURS DONT NOUS AVONS BESOIN ####### +void fetchRequiredChests() { + /* TODO */ +} + + + + + + +void MQTTconnect() { + while (!MQTTclient.connected()) { + Serial.print("Attente de la connexion MQTT...."); + String clientId = "TestClient-"; + clientId += String(random(0xffff), HEX); + + // test connexion + if (MQTTclient.connect(clientId.c_str(),"","")) { + Serial.println("connected"); + + // ####### 2.b. SOUSCRIPTION AUX TOPICS MQTT DE MON/MES JOUEURS ####### + /* TODO */ + + } else { // si echec affichage erreur + Serial.print("ECHEC, rc="); + Serial.print(MQTTclient.state()); + Serial.println(" nouvelle tentative dans 5 secondes"); + delay(5000); + } + } +} + +void MQTTcallback(char* topic, byte* payload, unsigned int length) { + Serial.print("Message MQTT reçu sur ["); + String topicString = String((char*)topic); + Serial.print(topicString); + Serial.println("] "); + payload[length] = '\0'; + + // ####### 2.b. "AIGUILLAGE" DES MESSAGES MQTT SELON LES TOPICS ####### + // Vous pouvez utiliser la fonction getSubTopicAtIndex(topicString, VOTRE_INDEX) + // Par exemple getSubTopicAtIndex(topicString, 1) retournera "AntoineRcbs" + // pour l'entrée topicString = "redTeam/AntoineRcbs/location" + /* TODO */ +} + + +// ####### 2.c. Décodage de la donnée (numéro du block de construction tenu) et mise à jour de l'affichage ####### +// On pourra prendre un DynamicJsonDocument de taille 128 +void onConstructionItemHeld(byte* payload, unsigned int length) { + /* TODO */ +} + + + +// ####### 2.c. Décodage du JSON et traitement sur les données extraites pour servo (+bonus affichage) ####### +// On pourra prendre un DynamicJsonDocument de taille 128 +void onPlayerLocationUpdate(byte* payload, unsigned int length) { + /* TODO */ +} + + + +void setup() { + initLedMatrix(); + compassServo.attach(D4, 300, 2700); + Serial.begin(115200); + + // ####### 1.a. CONNEXION AU WIFI ####### + /* TODO */ + + // ######## 1.c. REQUETE HTTP POUR PIXEL ART ######## + /* TODO */ + + // ######## 1.d. REQUETE HTTP POUR COFFRES ######## + /* TODO */ + + + // ####### 2.a. INITIALISATION DU SERVEUR ET DU CALLBACK MQTT ####### + /* TODO */ +} + +void loop() { + + // ####### 2.a. RECONNEXION ET POOLING LOOP MQTT ####### + /* TODO */ +} \ No newline at end of file