Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
projets:serveurwebsurbatterie:accueil [2019/12/28 21:22] laurent [Description] |
projets:serveurwebsurbatterie:accueil [2019/12/28 21:52] (Version actuelle) laurent [Code Arduino] |
||
---|---|---|---|
Ligne 4: | Ligne 4: | ||
* Licence : libre ! | * Licence : libre ! | ||
* Contexte : Noël | * Contexte : Noël | ||
- | * Fichiers : | + | * Fichiers : {{ : |
Ligne 12: | Ligne 12: | ||
Cadeau original et intriguant pour développeurs en herbe ou mordus d' | Cadeau original et intriguant pour développeurs en herbe ou mordus d' | ||
- | Les questions du quizz peuvent être facilement personnalisées et l' | + | Les questions du quizz peuvent être facilement personnalisées et le destinataire peut réutiliser le circuit en y flashant son propre code pour réaliser toutes sorte d' |
{{ : | {{ : | ||
Ligne 29: | Ligne 29: | ||
* le code arduino qui gère le wifi, le serveur web, valide les réponses et joue la mélodie en PWM sur le piezo | * le code arduino qui gère le wifi, le serveur web, valide les réponses et joue la mélodie en PWM sur le piezo | ||
* le code HTML/JS assure l' | * le code HTML/JS assure l' | ||
- | * les librairies externes ([[https:// | + | * les librairies externes ([[https:// |
==== Le SPIFFS ==== | ==== Le SPIFFS ==== | ||
Les ESP8266 comportent le plus souvent 4Mo de mémoire flash qui peut être répartie en une section réservée au code et une réservée au SPIFFS. Le SPIFFS ou //SPI FileSystem// | Les ESP8266 comportent le plus souvent 4Mo de mémoire flash qui peut être répartie en une section réservée au code et une réservée au SPIFFS. Le SPIFFS ou //SPI FileSystem// | ||
- | Lors du téléversement du code depuis l'IDE d' | + | |
+ | Lors du téléversement du code depuis l'IDE d' | ||
+ | |||
+ | Une fois le plugin installé et la taille du SPIFFS alloué, il est possible de téléverser les fichiers contenus dans le répertoire **data** à l' | ||
+ | <WRAP center round tip 60%> | ||
+ | Le téléversement peut échouer si le moniteur série est lancé, pensez à le fermer avant de téléverser. | ||
+ | </ | ||
+ | |||
+ | {{ : | ||
==== Code Arduino==== | ==== Code Arduino==== | ||
- | Pour téléverser le code depuis Arduino, la librairie ESP8266 ainsi que le plugin SPIFFS doivent être préalablement installés. Le code initialise le SPIFFS, indique les routes vers les librairies statiques stockées dans le SPIFFS, stocke et vérifie les bonnes réponses et joue la mélodie sur le piezo via la librairie " | + | Pour téléverser le code depuis Arduino, la librairie ESP8266 ainsi que le plugin SPIFFS doivent être préalablement installés. Le code initialise le SPIFFS, indique les routes vers les librairies statiques stockées dans le SPIFFS, crée un réseau WIFI sans mot de passe, stocke et vérifie les bonnes réponses et joue la mélodie sur le piezo via la librairie " |
- | Code arduino | + | ++++ Code ESP8266| |
+ | <code cpp> | ||
+ | /*Copyright 2019 Reso-nance Numérique < | ||
+ | This program is free software; you can redistribute it and/or modify | ||
+ | it under the terms of the GNU General Public License as published by | ||
+ | the Free Software Foundation; either version 2 of the License, or | ||
+ | (at your option) any later version. | ||
+ | | ||
+ | This program is distributed in the hope that it will be useful, | ||
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
+ | GNU General Public License for more details. | ||
+ | | ||
+ | You should have received a copy of the GNU General Public License | ||
+ | along with this program; if not, write to the Free Software | ||
+ | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||
+ | MA 02110-1301, USA. | ||
+ | |||
+ | |||
+ | -------------- Pinout -------------- | ||
+ | pin 12 (GPIO18) ------> Piezo + | ||
+ | (put a 220 Ohm resistor between piezo- and gnd) | ||
+ | this sketch needs at least 1M SPIFF (the code uses ~29ko of FLASH and SPIFFS files needs ~197ko) | ||
+ | */ | ||
+ | |||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include " | ||
+ | |||
+ | #define SERIAL_DEBUG | ||
+ | #define BUZZER_PIN D2 | ||
+ | |||
+ | IPAddress | ||
+ | // Default IP in AP mode is 192.168.4.1 | ||
+ | |||
+ | const char *ssid = " | ||
+ | // const char *password = " | ||
+ | const String goodAnswers[]={" | ||
+ | const unsigned int answerCount = 10; // since answers are stored as const Strings, we cannot use the sizeof(goodAnswers)/ | ||
+ | |||
+ | int wish_melody[] = { // "we wish you a merry christmas" | ||
+ | NOTE_B3, // theses notes will be converted to pitch in Hz by the pitches.h library | ||
+ | NOTE_F4, NOTE_F4, NOTE_G4, NOTE_F4, NOTE_E4, | ||
+ | NOTE_D4, NOTE_D4, NOTE_D4, | ||
+ | NOTE_G4, NOTE_G4, NOTE_A4, NOTE_G4, NOTE_F4, | ||
+ | NOTE_E4, NOTE_E4, NOTE_E4, | ||
+ | NOTE_A4, NOTE_A4, NOTE_B4, NOTE_A4, NOTE_G4, | ||
+ | NOTE_F4, NOTE_D4, NOTE_B3, NOTE_B3, | ||
+ | NOTE_D4, NOTE_G4, NOTE_E4, | ||
+ | NOTE_F4 | ||
+ | }; | ||
+ | |||
+ | int wish_tempo[] = {// relative note durations defining the rythme of the melody | ||
+ | 4, | ||
+ | 4, 8, 8, 8, 8, | ||
+ | 4, 4, 4, | ||
+ | 4, 8, 8, 8, 8, | ||
+ | 4, 4, 4, | ||
+ | 4, 8, 8, 8, 8, | ||
+ | 4, 4, 8, 8, | ||
+ | 4, 4, 4, | ||
+ | 2 | ||
+ | }; | ||
+ | |||
+ | ESP8266WebServer server(80); | ||
+ | |||
+ | #ifdef SERIAL_DEBUG // I defined theses two functions to avoid using "# | ||
+ | #define debugPrint(x) | ||
+ | #define debugPrintln(x) | ||
+ | #else | ||
+ | #define debugPrint(x) | ||
+ | #define debugPrintln(x) | ||
+ | #endif | ||
+ | |||
+ | void handleNotFound() {// 404 manager, no fancy html here, just plain text sent by the server | ||
+ | String message = "File Not Found\n\n"; | ||
+ | message += "URI: "; | ||
+ | message += server.uri();// | ||
+ | message += " | ||
+ | message += ( server.method() == HTTP_GET ) ? " | ||
+ | message += " | ||
+ | message += server.args(); | ||
+ | message += " | ||
+ | for ( uint8_t i = 0; i < server.args(); | ||
+ | message += " " + server.argName ( i ) + ": " + server.arg ( i ) + " | ||
+ | } | ||
+ | server.send ( 404, " | ||
+ | } | ||
+ | |||
+ | void setup() { | ||
+ | pinMode(LED_BUILTIN, | ||
+ | digitalWrite(LED_BUILTIN, | ||
+ | pinMode(BUZZER_PIN, | ||
+ | pinMode(BUZZER_PIN, | ||
+ | | ||
+ | #ifdef SERIAL_DEBUG | ||
+ | Serial.begin(115200); | ||
+ | #endif | ||
+ | debugPrintln(); | ||
+ | debugPrintln(" | ||
+ | |||
+ | //set-up the custom IP address | ||
+ | WiFi.mode(WIFI_AP_STA); | ||
+ | WiFi.softAPConfig(apIP, | ||
+ | |||
+ | WiFi.softAP(ssid); | ||
+ | // WiFi.softAP(ssid, | ||
+ | WiFi.hostname(" | ||
+ | IPAddress myIP = WiFi.softAPIP(); | ||
+ | debugPrint(" | ||
+ | debugPrintln(myIP); | ||
+ | |||
+ | SPIFFS.begin(); | ||
+ | |||
+ | // server routes, much like PhP, each URL is linked to a function | ||
+ | server.onNotFound ( handleNotFound ); | ||
+ | server.on("/", | ||
+ | server.on("/ | ||
+ | server.on("/ | ||
+ | server.on(" | ||
+ | server.on("/ | ||
+ | server.on("/ | ||
+ | server.on(" | ||
+ | server.on(" | ||
+ | server.on("/ | ||
+ | server.on("/ | ||
+ | server.on(" | ||
+ | server.on("/ | ||
+ | server.on("/ | ||
+ | server.begin(); | ||
+ | debugPrintln(" | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | server.handleClient();// | ||
+ | } | ||
+ | |||
+ | void validateAnswers(){ // this function is called when the user click on the validate button on the UI | ||
+ | String questionsID[answerCount];// | ||
+ | unsigned int validatedAnswers = 0;// this will increment each time an answer is counted as valid | ||
+ | for (unsigned int i=0; i< | ||
+ | const String test = server.arg(String(i)); | ||
+ | if (test == goodAnswers[i]) validatedAnswers++; | ||
+ | else debugPrintln(" | ||
+ | } | ||
+ | debugPrintln(String(validatedAnswers)+"/" | ||
+ | // now will contruct a String containing JSON data {" | ||
+ | String json = " | ||
+ | server.send(200, | ||
+ | if (validatedAnswers == answerCount) {// if every answer is correct | ||
+ | digitalWrite(LED_BUILTIN, | ||
+ | singWeWishYou(2); | ||
+ | digitalWrite(LED_BUILTIN, | ||
+ | } | ||
+ | else digitalWrite(LED_BUILTIN, | ||
+ | } | ||
+ | |||
+ | void singWeWishYou(int loops) { // this function will play "we wish you a merry christmas" | ||
+ | // iterate over the notes of the melody: | ||
+ | debugPrintln(" | ||
+ | int size = sizeof(wish_melody) / sizeof(int); | ||
+ | for (unsigned int i=0; i<loops; i++) { | ||
+ | for (int thisNote = 0; thisNote < size; thisNote++) { | ||
+ | | ||
+ | // to calculate the note duration, take one second | ||
+ | // divided by the note type. | ||
+ | //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. | ||
+ | int noteDuration = 1000 / wish_tempo[thisNote]; | ||
+ | | ||
+ | tone(BUZZER_PIN, | ||
+ | | ||
+ | // to distinguish the notes, set a minimum time between them. | ||
+ | // the note's duration + 30% seems to work well: | ||
+ | int pauseBetweenNotes = noteDuration * 1.30; | ||
+ | ESP.wdtFeed();// | ||
+ | delay(pauseBetweenNotes); | ||
+ | | ||
+ | // stop the tone playing: | ||
+ | noTone(BUZZER_PIN); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // the functions below are routes to files stored in the SPIFFS | ||
+ | void fileindex() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | // we have zipped the static library to save space on the ESP8266 flash | ||
+ | void bootstrapCSS() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | void bootstrapThemeCSS() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | void popper() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | void bootstrapJS() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | |||
+ | void jquery() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | void favicon() | ||
+ | { | ||
+ | File file = SPIFFS.open("/ | ||
+ | size_t sent = server.streamFile(file, | ||
+ | } | ||
+ | </ | ||
+ | ++++ | ||
==== HTML/JS ==== | ==== HTML/JS ==== | ||
Pour plus de flexibilité, | Pour plus de flexibilité, | ||
Ligne 44: | Ligne 275: | ||
...]</ | ...]</ | ||
Le code HTML est généré dynamiquement pour créer une div de la classe **well** dans un [[https:// | Le code HTML est généré dynamiquement pour créer une div de la classe **well** dans un [[https:// | ||
- | html/JS | + | ++++ Code HTML et JS| |
+ | <code javascript> | ||
+ | <!-- Copyright 2019 Reso-nance Numérique < | ||
+ | |||
+ | This program is free software; you can redistribute it and/or modify | ||
+ | it under the terms of the GNU General Public License as published by | ||
+ | the Free Software Foundation; either version 2 of the License, or | ||
+ | (at your option) any later version. | ||
+ | |||
+ | This program is distributed in the hope that it will be useful, | ||
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
+ | GNU General Public License for more details. | ||
+ | |||
+ | You should have received a copy of the GNU General Public License | ||
+ | along with this program; if not, write to the Free Software | ||
+ | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||
+ | MA 02110-1301, USA. --> | ||
+ | |||
+ | <html lang=" | ||
+ | < | ||
+ | <meta charset=" | ||
+ | <meta http-equiv=' | ||
+ | <meta name=" | ||
+ | <meta name=" | ||
+ | <meta name=" | ||
+ | <link rel=" | ||
+ | < | ||
+ | |||
+ | <link href="/ | ||
+ | <link href="/ | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | <div class=" | ||
+ | <h1 style=" | ||
+ | <!-- this div will be dynamically filled with questions and answers --> | ||
+ | <div id=" | ||
+ | <!-- the validation button will send every answer as a POST request to the ESP8266 webserver --> | ||
+ | <button type=" | ||
+ | <!-- this div will contain the response of the server after validation of the answers --> | ||
+ | <div id=" | ||
+ | </ | ||
+ | |||
+ | <script src="/ | ||
+ | <script src="/ | ||
+ | <!-- since the JS is quite small, we may as well write it in the same file as the HTML --> | ||
+ | <script type=" | ||
+ | |||
+ | $(document).ready(function() { | ||
+ | console.log(" | ||
+ | var quizzItems = [] // this global var will contain objects with questions and answers | ||
+ | // each item is an object like {question:" | ||
+ | quizzItems.push({question:' | ||
+ | "< | ||
+ | "< | ||
+ | "< | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | "while i = 1 to 10", | ||
+ | "while (i <= 10) ", | ||
+ | "while (i <= 10; i++)" | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | '" | ||
+ | '" | ||
+ | '" | ||
+ | "all of the above" | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ]}); | ||
+ | quizzItems.push({question:' | ||
+ | "All div elements", | ||
+ | "The first div element" | ||
+ | ]}); | ||
+ | quizzItems.push({question:' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | "un langage utilisé par les sorcières durant les sabbats obscurs", | ||
+ | "un outil cryptique mais flexible pour manipuler des chaines", | ||
+ | "un mal nécessaire", | ||
+ | " | ||
+ | ]}); | ||
+ | quizzItems.push({question:" | ||
+ | "une suite logicielle libre coomprenant Linux, Apache MySQL, Php ", | ||
+ | "un ordinateur enchanté invoquant un génie pour générer la documentation de chaque ligne de code non-documentée trainant par là" | ||
+ | ]}); | ||
+ | |||
+ | var currentResponses=new Array(quizzItems.length).fill(0) // by default, the first answer (index 0) of every question is checked | ||
+ | |||
+ | for (var i=0; i< | ||
+ | $("# | ||
+ | } | ||
+ | |||
+ | |||
+ | function generateQuestion(quizz, | ||
+ | var html = $('< | ||
+ | html.append('< | ||
+ | var answerIndex = 0; | ||
+ | quizz.answers.forEach(function(answer){ // for each possible answer : | ||
+ | const checked = (answerIndex == 0) ? " checked" | ||
+ | // add a radio button corresponding to the answer. Only one answer can be checked at the same time, thanks to the name attribute | ||
+ | html.append('< | ||
+ | answerIndex++; | ||
+ | }); | ||
+ | html.append("</ | ||
+ | return html; | ||
+ | } | ||
+ | |||
+ | $(document).on(' | ||
+ | const answerValue = $(this).attr(' | ||
+ | const questionIndex = parseInt($(this).closest(" | ||
+ | currentResponses[questionIndex] = answerValue; | ||
+ | }); | ||
+ | |||
+ | $("# | ||
+ | var postString = "/ | ||
+ | for (var i=0; i< | ||
+ | postString += i.toString()+ " | ||
+ | // for exemple, if question two has it's second answer (index=1) checked, add " | ||
+ | if (i < currentResponses.length - 1) postString += "&"; | ||
+ | } | ||
+ | // poststring should now look like this : "/ | ||
+ | console.log(postString); | ||
+ | $.getJSON(postString, | ||
+ | if (data.goodAnswers == data.totalQuestions) {// if the number of good answers matches the number of questions | ||
+ | $("# | ||
+ | } | ||
+ | else $("# | ||
+ | }); | ||
+ | }); | ||
+ | |||
+ | }); | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | ++++ |