This shows you the differences between two versions of the page.
|
pm:prj2023:adarmaz:kings-cup [2023/05/30 02:56] mihnea.branzeu [Descriere generală] |
pm:prj2023:adarmaz:kings-cup [2023/05/30 09:24] (current) mihnea.branzeu [Rezultate Obţinute] |
||
|---|---|---|---|
| Line 61: | Line 61: | ||
| ===== Software Design ===== | ===== Software Design ===== | ||
| + | Librarii folosite: | ||
| + | * **LiquidCrystal_I2C.h** - folosita pentru interfatarea cu LCD-ul | ||
| + | * **esp_now.h** si **WiFi.h** - folosite pentru comunicarea inter-modul | ||
| + | In cadrul proiectului am fost nevoit sa scriu doua programe separate, unul care sa ruleze pe modulul master, iar celalalt pentru a rula pe fiecare dintre modulele player. | ||
| - | <note tip> | + | ==== Comunicarea inter-ESP32 ==== |
| - | Descrierea codului aplicaţiei (firmware): | + | Pentru a permite comunicarea intre cele 4 module ESP32 am folosit biblioteca **esp-now.h**. Aceasta foloseste adresa MAC a ESPului pentru a identifica statia in retea. Astfel, am avut nevoie de un script cu ajutorul caruia sa aflu adresa MAC a fiecarui modul: |
| - | * mediu de dezvoltare (if any) (e.g. AVR Studio, CodeVisionAVR) | + | |
| - | * librării şi surse 3rd-party (e.g. Procyon AVRlib) | + | <code cpp> |
| - | * algoritmi şi structuri pe care plănuiţi să le implementaţi | + | #include "WiFi.h" |
| - | * (etapa 3) surse şi funcţii implementate | + | |
| - | </note> | + | void setup() { |
| + | // Setup Serial Monitor | ||
| + | Serial.begin(9600); | ||
| + | |||
| + | // Put ESP32 into Station mode | ||
| + | WiFi.mode(WIFI_MODE_STA); | ||
| + | |||
| + | // Print MAC Address to Serial monitor | ||
| + | Serial.print("MAC Address: "); | ||
| + | Serial.println(WiFi.macAddress()); | ||
| + | } | ||
| + | |||
| + | void loop() {} | ||
| + | </code> | ||
| + | |||
| + | |||
| + | Avand adresa MAC, am trecut la a construi un standard de comunicare. Am obtinut urmatoarea configuratie: | ||
| + | |||
| + | <code cpp> | ||
| + | #define MESSAGE_DRINK 0 | ||
| + | #define MESSAGE_CONFIRM 1 | ||
| + | #define MESSAGE_SCREAM 2 | ||
| + | #define MESSAGE_SAY 3 | ||
| + | #define MESSAGE_GO_NEXT 4 | ||
| + | #define MESSAGE_THUMB_MASTER 5 | ||
| + | #define MESSAGE_WATERFALL_GO 6 | ||
| + | #define MESSAGE_WATERFALL_STOP 7 | ||
| + | #define MESSAGE_WATERFALL_GO_INITIATOR 8 | ||
| + | |||
| + | typedef struct message_t { | ||
| + | int sender; | ||
| + | int receiver; | ||
| + | int messageType; | ||
| + | } message_t; | ||
| + | </code> | ||
| + | |||
| + | Cu ajutorul define-urilor, am putut sa impart mesajele in mai multe categorii, acestea completand campul **messageType** din cadrul structurii. De asemenea, structura **message_t** contine layout-ul unui mesaj intre doua module: senderul (sursa mesajului), receiverul (destinatia mesajului) si tipul mesajului. Acest segment de cod trebuie sa se regaseasca atat pe master, cat si pe player. | ||
| + | |||
| + | Mai departe, cu ajutorul functiilor **OnDataSent()**, **OnDataReceived()** si **esp_now_send()**, oferite de biblioteca, am putut realiza comunicarea intre module. | ||
| + | |||
| + | ==== Cod Master ==== | ||
| + | Modulul Master este realizat sub forma unui state-machine, avand urmatoarele stari: | ||
| + | <code cpp> | ||
| + | #define NEW_GAME_STATE 0 | ||
| + | #define NEW_CARD_STATE 1 | ||
| + | #define WAITING_FOR_DRINKS_STATE 2 | ||
| + | #define GAME_OVER_STATE 3 | ||
| + | #define IDLE_STATE 4 | ||
| + | #define CHOOSING_SOMEONE_STATE 5 | ||
| + | #define COUNTDOWN_STATE 6 | ||
| + | #define THUMB_MASTER_STATE 7 | ||
| + | #define WATERFALL_STATE 8 | ||
| + | </code> | ||
| + | Logica este alcatuita din urmatorii pasi: | ||
| + | - O carte este generata random | ||
| + | - Functia corespunzatoare cartii este apelata si provocarea incepe | ||
| + | - Playerii care trebuie sa bea sunt anuntati, si se asteapta ca ei sa execute provocarea | ||
| + | - Atunci cand se doreste trecerea la urmatoarea carte, functia de verificare este apelata | ||
| + | - Daca toti jucatorii au indeplinit provocarea, se revine la pasul **1** | ||
| + | |||
| + | Un jucator este definit sub forma unei structuri astfel: | ||
| + | <code cpp> | ||
| + | // The structure containing info about players | ||
| + | typedef struct player_t { | ||
| + | uint8_t macAdress[6]; // The MAC address used for communication | ||
| + | int gender; // Used for special challenges | ||
| + | bool shouldDrink; // Flag for keeping track of the status for each player | ||
| + | bool thumbMaster; // Flag for the thumb master challenge | ||
| + | } player_t; | ||
| + | </code> | ||
| + | |||
| + | Codul integral al modulului master: | ||
| + | <code cpp> | ||
| + | #include <LiquidCrystal_I2C.h> | ||
| + | #include <esp_now.h> | ||
| + | #include <WiFi.h> | ||
| + | |||
| + | // Pin Setup | ||
| + | #define BUTTON_1_PIN 33 | ||
| + | #define BUTTON_2_PIN 25 | ||
| + | #define BUTTON_3_PIN 26 | ||
| + | #define BUTTON_NEXT_PIN 32 | ||
| + | |||
| + | // States definition | ||
| + | #define NEW_GAME_STATE 0 | ||
| + | #define NEW_CARD_STATE 1 | ||
| + | #define WAITING_FOR_DRINKS_STATE 2 | ||
| + | #define GAME_OVER_STATE 3 | ||
| + | #define IDLE_STATE 4 | ||
| + | #define CHOOSING_SOMEONE_STATE 5 | ||
| + | #define COUNTDOWN_STATE 6 | ||
| + | #define THUMB_MASTER_STATE 7 | ||
| + | #define WATERFALL_STATE 8 | ||
| + | |||
| + | #define CARDS_PER_NUMBER 4 | ||
| + | #define NUMBER_OF_PLAYERS 3 | ||
| + | |||
| + | // Message types | ||
| + | #define MESSAGE_DRINK 0 | ||
| + | #define MESSAGE_CONFIRM 1 | ||
| + | #define MESSAGE_SCREAM 2 | ||
| + | #define MESSAGE_SAY 3 | ||
| + | #define MESSAGE_GO_NEXT 4 | ||
| + | #define MESSAGE_THUMB_MASTER 5 | ||
| + | #define MESSAGE_WATERFALL_GO 6 | ||
| + | #define MESSAGE_WATERFALL_STOP 7 | ||
| + | #define MESSAGE_WATERFALL_GO_INITIATOR 8 | ||
| + | |||
| + | #define GIRL 0 | ||
| + | #define BOY 1 | ||
| + | |||
| + | #define PLAYER_1 0 | ||
| + | #define PLAYER_2 1 | ||
| + | #define PLAYER_3 2 | ||
| + | #define MASTER 3 | ||
| + | |||
| + | |||
| + | // The structure containing info about players | ||
| + | typedef struct player_t { | ||
| + | uint8_t macAdress[6]; // The MAC address used for communication | ||
| + | int gender; // Used for special challenges | ||
| + | bool shouldDrink; // Flag for keeping track of the status for each player | ||
| + | bool thumbMaster; // Flag for the thumb master challenge | ||
| + | } player_t; | ||
| + | |||
| + | // The structure containing the layout of a message | ||
| + | typedef struct message_t { | ||
| + | int sender; | ||
| + | int receiver; | ||
| + | int messageType; | ||
| + | } message_t; | ||
| + | |||
| + | // The players array | ||
| + | player_t players[3]; | ||
| + | |||
| + | // The left amount of each card | ||
| + | int cardFrequencies[15]; | ||
| + | int cardsLeft = (CARDS_PER_NUMBER) * 12; | ||
| + | |||
| + | // The current state of the game | ||
| + | int currentState; | ||
| + | int currentTurn; | ||
| + | |||
| + | // Variables used for countdown | ||
| + | double a = millis(); | ||
| + | double i = 0; | ||
| + | double c; | ||
| + | bool shouldRun = false; | ||
| + | bool shouldReset = false; | ||
| + | int currentToAnswer; | ||
| + | |||
| + | // Variable used for thumb master | ||
| + | int thumbMasterCount; | ||
| + | |||
| + | // Peer info structure | ||
| + | esp_now_peer_info_t peerInfo; | ||
| + | |||
| + | LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x3F for a 16 chars and 2 line display | ||
| + | |||
| + | // Callback when data is sent | ||
| + | void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { | ||
| + | char macStr[18]; | ||
| + | Serial.print("Packet to: "); | ||
| + | // Copies the sender mac address to a string | ||
| + | snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", | ||
| + | mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); | ||
| + | Serial.print(macStr); | ||
| + | Serial.print(" send status:\t"); | ||
| + | Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); | ||
| + | } | ||
| + | |||
| + | // Callback function executed when data is received | ||
| + | void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { | ||
| + | message_t message; | ||
| + | |||
| + | memcpy(&message, incomingData, sizeof(message_t)); | ||
| + | Serial.print("Data received: "); | ||
| + | Serial.println(len); | ||
| + | Serial.print("Sender: "); | ||
| + | Serial.println(message.sender); | ||
| + | Serial.print("Message type: "); | ||
| + | Serial.println(message.messageType == 0 ? "DRINK" : "CONFIRM"); | ||
| + | |||
| + | // Check the type of message | ||
| + | if (message.messageType == MESSAGE_CONFIRM) { | ||
| + | // Mark the drink flag for the player | ||
| + | players[message.sender].shouldDrink = false; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_GO_NEXT) { | ||
| + | // Send the say message to the next player | ||
| + | shouldReset = true; | ||
| + | player_t nextPlayer = players[(message.sender + 1) % NUMBER_OF_PLAYERS]; | ||
| + | currentToAnswer = (message.sender + 1) % NUMBER_OF_PLAYERS; | ||
| + | sendMessage((message.sender + 1) % NUMBER_OF_PLAYERS, MESSAGE_SAY, nextPlayer.macAdress); | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_THUMB_MASTER) { | ||
| + | // Mark the player | ||
| + | players[message.sender].thumbMaster = true; | ||
| + | thumbMasterCount++; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_WATERFALL_STOP) { | ||
| + | if ((message.sender + 1) % NUMBER_OF_PLAYERS != currentTurn) { | ||
| + | player_t nextPlayer = players[(message.sender + 1) % NUMBER_OF_PLAYERS]; | ||
| + | sendMessage((message.sender + 1) % NUMBER_OF_PLAYERS, MESSAGE_WATERFALL_STOP, nextPlayer.macAdress); | ||
| + | } | ||
| + | else { | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void sendMessage(int receiver, int messageType, uint8_t *macAdress) { | ||
| + | // Create the message | ||
| + | message_t message = {MASTER, receiver, messageType}; | ||
| + | |||
| + | // Send the message | ||
| + | esp_err_t result = esp_now_send(macAdress, (uint8_t *) &message, sizeof(message_t)); | ||
| + | |||
| + | // Check the result | ||
| + | if (result == ESP_OK) { | ||
| + | Serial.println("Sent with success"); | ||
| + | } | ||
| + | else { | ||
| + | Serial.println("Error sending the data"); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void lcdPrintAnimation() { | ||
| + | lcd.clear(); | ||
| + | lcd.setCursor(2, 0); | ||
| + | lcd.print(" PLAYER "); | ||
| + | lcd.setCursor(10, 0); | ||
| + | lcd.print(currentTurn + 1); | ||
| + | delay(2000); | ||
| + | lcd.clear(); | ||
| + | int i, j; | ||
| + | lcd.setCursor(0, 0); | ||
| + | for (int i = 0; i < 16; i++) { | ||
| + | for (int j = 0; j < i; j++) { | ||
| + | lcd.setCursor(j, 0); | ||
| + | lcd.print("="); | ||
| + | lcd.setCursor(15 - j, 1); | ||
| + | lcd.print("="); | ||
| + | } | ||
| + | delay(100); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void lcdPrint(char *message, int row, int shouldClear) { | ||
| + | if (shouldClear) { | ||
| + | lcd.clear(); | ||
| + | } | ||
| + | lcd.setCursor(2, row); | ||
| + | lcd.print(message); | ||
| + | } | ||
| + | |||
| + | void twoChallenge() { | ||
| + | // Print the message on the LCD | ||
| + | lcdPrint("2 IS YOU", 0, 1); | ||
| + | lcdPrint("Choose Someone", 1, 0); | ||
| + | |||
| + | // Change the state to choosing someone | ||
| + | currentState = CHOOSING_SOMEONE_STATE; | ||
| + | } | ||
| + | |||
| + | void threeChallenge() { | ||
| + | // Print the message on the LCD | ||
| + | lcdPrint("3 IS ME", 0, 1); | ||
| + | lcdPrint(" Drink!!", 1, 0); | ||
| + | |||
| + | // Mark the player as drinker | ||
| + | players[currentTurn].shouldDrink = true; | ||
| + | |||
| + | // Send the message | ||
| + | sendMessage(currentTurn, MESSAGE_DRINK, players[currentTurn].macAdress); | ||
| + | } | ||
| + | |||
| + | void fourChallenge() { | ||
| + | lcdPrint("4 IS GIRLS", 0, 1); | ||
| + | lcdPrint("Girls Drink", 1, 0); | ||
| + | |||
| + | // Search for the girls | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | if (players[i].gender == GIRL) { | ||
| + | // Mark the player as drinker | ||
| + | players[i].shouldDrink = true; | ||
| + | |||
| + | // Send the message | ||
| + | sendMessage(i, MESSAGE_DRINK, players[i].macAdress); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | void fiveChallenge() { | ||
| + | lcdPrint(" THUMB MASTER", 0, 1); | ||
| + | lcdPrint(" GO", 1, 0); | ||
| + | |||
| + | // Reset the thumb master flag for players | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | players[i].thumbMaster = false; | ||
| + | } | ||
| + | |||
| + | currentState = THUMB_MASTER_STATE; | ||
| + | |||
| + | // Send the message to the players | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | sendMessage(i, MESSAGE_THUMB_MASTER, players[i].macAdress); | ||
| + | } | ||
| + | |||
| + | // Initialize the counter | ||
| + | thumbMasterCount = 0; | ||
| + | } | ||
| + | |||
| + | void sixChallenge() { | ||
| + | lcdPrint("6 IS GUYS", 0, 1); | ||
| + | lcdPrint("Boys Drink", 1, 0); | ||
| + | |||
| + | // Search for the boys | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | if (players[i].gender == BOY) { | ||
| + | // Mark the player as drinker | ||
| + | players[i].shouldDrink = true; | ||
| + | |||
| + | // Send the message | ||
| + | sendMessage(i, MESSAGE_DRINK, players[i].macAdress); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void eightChallenge() { | ||
| + | lcdPrint("8 IS MATE", 0, 1); | ||
| + | lcdPrint("Choose Someone", 1, 0); | ||
| + | |||
| + | // Mark the player as drinker | ||
| + | players[currentTurn].shouldDrink = true; | ||
| + | // Send the message | ||
| + | sendMessage(currentTurn, MESSAGE_DRINK, players[currentTurn].macAdress); | ||
| + | |||
| + | // Change the state to choosing someone | ||
| + | currentState = CHOOSING_SOMEONE_STATE; | ||
| + | } | ||
| + | |||
| + | void nineChallenge() { | ||
| + | lcdPrint("9 IS RHYME", 0, 1); | ||
| + | lcdPrint("BEGIN", 1, 0); | ||
| + | |||
| + | delay(2000); | ||
| + | |||
| + | // Update the current state | ||
| + | currentState = COUNTDOWN_STATE; | ||
| + | currentToAnswer = currentTurn; | ||
| + | sendMessage(currentTurn, MESSAGE_SAY, players[currentTurn].macAdress); | ||
| + | |||
| + | } | ||
| + | |||
| + | void tenChallenge() { | ||
| + | lcdPrint("10 IS CATEGORY", 0, 1); | ||
| + | lcdPrint("BEGIN", 1, 0); | ||
| + | |||
| + | delay(2000); | ||
| + | |||
| + | // Update the current state | ||
| + | currentState = COUNTDOWN_STATE; | ||
| + | currentToAnswer = currentTurn; | ||
| + | sendMessage(currentTurn, MESSAGE_SAY, players[currentTurn].macAdress); | ||
| + | } | ||
| + | |||
| + | void JChallenge() { | ||
| + | lcdPrint("J IS RULE", 0, 1); | ||
| + | lcdPrint("Choose a Rule", 1, 0); | ||
| + | |||
| + | } | ||
| + | |||
| + | void QChallenge() { | ||
| + | lcdPrint("Q IS QUESTION", 0, 1); | ||
| + | } | ||
| + | |||
| + | void KChallenge() { | ||
| + | lcdPrint("K IS EVERYONE", 0, 1); | ||
| + | lcdPrint("Everyone Drinks", 1, 0); | ||
| + | |||
| + | // Mark everyone as drinker | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | // Mark the player as drinker | ||
| + | players[i].shouldDrink = true; | ||
| + | |||
| + | // Send the message | ||
| + | sendMessage(i, MESSAGE_DRINK, players[i].macAdress); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void AChallenge() { | ||
| + | lcdPrint("A IS WATERFALL", 0, 1); | ||
| + | |||
| + | currentState = WATERFALL_STATE; | ||
| + | |||
| + | // Send the go message to the initiator | ||
| + | sendMessage(currentTurn, MESSAGE_WATERFALL_GO_INITIATOR, players[currentTurn].macAdress); | ||
| + | // Send the go message to all the players | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | if (i == currentTurn) { | ||
| + | continue; | ||
| + | } | ||
| + | sendMessage(i, MESSAGE_WATERFALL_GO, players[i].macAdress); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void startRound(int card) { | ||
| + | // Update the game state | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | |||
| + | // Check the value of the card and proceed accordingly | ||
| + | switch (card) { | ||
| + | case 2: | ||
| + | twoChallenge(); | ||
| + | break; | ||
| + | case 3: | ||
| + | threeChallenge(); | ||
| + | break; | ||
| + | case 4: | ||
| + | fourChallenge(); | ||
| + | break; | ||
| + | case 5: | ||
| + | fiveChallenge(); | ||
| + | break; | ||
| + | case 6: | ||
| + | sixChallenge(); | ||
| + | break; | ||
| + | case 8: | ||
| + | eightChallenge(); | ||
| + | break; | ||
| + | case 9: | ||
| + | nineChallenge(); | ||
| + | break; | ||
| + | case 10: | ||
| + | tenChallenge(); | ||
| + | break; | ||
| + | case 11: | ||
| + | JChallenge(); | ||
| + | break; | ||
| + | case 12: | ||
| + | QChallenge(); | ||
| + | break; | ||
| + | case 13: | ||
| + | KChallenge(); | ||
| + | break; | ||
| + | case 14: | ||
| + | AChallenge(); | ||
| + | break; | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | int canMoveToNext() { | ||
| + | int ret = 1; | ||
| + | // Check the flag for each player | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | // Check if the player drank | ||
| + | if (players[i].shouldDrink) { | ||
| + | // Send the alert message | ||
| + | sendMessage(i, MESSAGE_SCREAM, players[i].macAdress); | ||
| + | ret = 0; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Everybody drank | ||
| + | return ret; | ||
| + | } | ||
| + | |||
| + | void setup() { | ||
| + | Serial.begin(9600); | ||
| + | |||
| + | // Initialize the players | ||
| + | players[0].macAdress[0] = 0xD4; | ||
| + | players[0].macAdress[1] = 0xD4; | ||
| + | players[0].macAdress[2] = 0xDA; | ||
| + | players[0].macAdress[3] = 0x5B; | ||
| + | players[0].macAdress[4] = 0x41; | ||
| + | players[0].macAdress[5] = 0x88; | ||
| + | players[0].gender = GIRL; | ||
| + | |||
| + | players[1].macAdress[0] = 0xD4; | ||
| + | players[1].macAdress[1] = 0xD4; | ||
| + | players[1].macAdress[2] = 0xDA; | ||
| + | players[1].macAdress[3] = 0x5A; | ||
| + | players[1].macAdress[4] = 0x5F; | ||
| + | players[1].macAdress[5] = 0x3C; | ||
| + | players[1].gender = BOY; | ||
| + | |||
| + | players[2].macAdress[0] = 0xD4; | ||
| + | players[2].macAdress[1] = 0xD4; | ||
| + | players[2].macAdress[2] = 0xDA; | ||
| + | players[2].macAdress[3] = 0x5A; | ||
| + | players[2].macAdress[4] = 0x66; | ||
| + | players[2].macAdress[5] = 0xEC; | ||
| + | players[2].gender = BOY; | ||
| + | |||
| + | // Initialize ESP NOW stuff | ||
| + | WiFi.mode(WIFI_STA); | ||
| + | if (esp_now_init() != ESP_OK) { | ||
| + | Serial.println("Error initializing ESP NOW"); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | esp_now_register_send_cb(OnDataSent); | ||
| + | esp_now_register_recv_cb(OnDataRecv); | ||
| + | |||
| + | |||
| + | // Register peers | ||
| + | peerInfo.channel = 0; | ||
| + | peerInfo.encrypt = false; | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | memcpy(peerInfo.peer_addr, players[i].macAdress, 6); | ||
| + | if (esp_now_add_peer(&peerInfo) != ESP_OK){ | ||
| + | Serial.println("Failed to add peer"); | ||
| + | return; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // LCD CODE | ||
| + | lcd.init(); | ||
| + | lcd.clear(); | ||
| + | lcd.backlight(); | ||
| + | |||
| + | // Print a message on both lines of the LCD. | ||
| + | lcd.setCursor(2,0); //Set cursor to character 2 on line 0 | ||
| + | lcd.print("King's Cup!"); | ||
| + | |||
| + | lcd.setCursor(2,1); //Move cursor to character 2 on line 1 | ||
| + | lcd.print("Press NEXT"); | ||
| + | //--------------------------- | ||
| + | |||
| + | //BUTTON SETUP | ||
| + | pinMode(BUTTON_1_PIN, INPUT_PULLUP); | ||
| + | pinMode(BUTTON_2_PIN, INPUT_PULLUP); | ||
| + | pinMode(BUTTON_3_PIN, INPUT_PULLUP); | ||
| + | pinMode(BUTTON_NEXT_PIN, INPUT_PULLUP); | ||
| + | |||
| + | //---------------------------- | ||
| + | |||
| + | // Initialize the card frequencies | ||
| + | for (int i = 2; i < 15; i++) { | ||
| + | cardFrequencies[i] = CARDS_PER_NUMBER; | ||
| + | } | ||
| + | // 7 card won't be played | ||
| + | cardFrequencies[7] = 0; | ||
| + | |||
| + | // Initialize the current state of the game | ||
| + | currentState = NEW_GAME_STATE; | ||
| + | currentTurn = 0; | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | if (currentState == NEW_GAME_STATE) { | ||
| + | // Check if the button was pressed | ||
| + | int nextButtonValue = digitalRead(BUTTON_NEXT_PIN); | ||
| + | if (!nextButtonValue) { | ||
| + | // Switch into the new card state | ||
| + | currentState = NEW_CARD_STATE; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == NEW_CARD_STATE) { | ||
| + | // Check if game should be over | ||
| + | if (cardsLeft == 0) { | ||
| + | currentState = GAME_OVER_STATE; | ||
| + | } else { | ||
| + | lcdPrintAnimation(); | ||
| + | // Generate a new card | ||
| + | int cardValue; | ||
| + | while (1) { | ||
| + | cardValue = random(2, 15); | ||
| + | if (cardFrequencies[cardValue] > 0) { | ||
| + | cardFrequencies[cardValue]--; | ||
| + | cardsLeft--; | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | Serial.print("Card Value: "); | ||
| + | Serial.println(cardValue); | ||
| + | |||
| + | // Start the new round depening on the card | ||
| + | startRound(cardValue); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == WAITING_FOR_DRINKS_STATE) { | ||
| + | // Check the next button state | ||
| + | int nextButtonValue = digitalRead(BUTTON_NEXT_PIN); | ||
| + | if (!nextButtonValue) { | ||
| + | // Check if everybody is ready | ||
| + | if (canMoveToNext()) { | ||
| + | currentState = NEW_CARD_STATE; | ||
| + | currentTurn = (currentTurn + 1) % NUMBER_OF_PLAYERS; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == CHOOSING_SOMEONE_STATE) { | ||
| + | // Check the input on the buttons | ||
| + | int button1Value = digitalRead(BUTTON_1_PIN); | ||
| + | int button2Value = digitalRead(BUTTON_2_PIN); | ||
| + | int button3Value = digitalRead(BUTTON_3_PIN); | ||
| + | |||
| + | if (!button1Value) { | ||
| + | players[PLAYER_1].shouldDrink = true; | ||
| + | sendMessage(PLAYER_1, MESSAGE_DRINK, players[PLAYER_1].macAdress); | ||
| + | // Update the current state | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | } else if (!button2Value) { | ||
| + | players[PLAYER_2].shouldDrink = true; | ||
| + | sendMessage(PLAYER_2, MESSAGE_DRINK, players[PLAYER_2].macAdress); | ||
| + | // Update the current state | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | } else if (!button3Value) { | ||
| + | players[PLAYER_3].shouldDrink = true; | ||
| + | sendMessage(PLAYER_3, MESSAGE_DRINK, players[PLAYER_3].macAdress); | ||
| + | // Update the current state | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == COUNTDOWN_STATE) { | ||
| + | lcd.clear(); | ||
| + | a = millis(); | ||
| + | shouldRun = true; | ||
| + | while (shouldRun) { | ||
| + | c = millis(); | ||
| + | i = (c - a) / 1000; | ||
| + | if (shouldReset) { | ||
| + | a = millis(); | ||
| + | shouldReset = false; | ||
| + | } | ||
| + | if (i >= 5) { | ||
| + | shouldRun = false; | ||
| + | } | ||
| + | lcd.setCursor(5, 0); | ||
| + | lcd.print(i); | ||
| + | delay(100); | ||
| + | } | ||
| + | players[currentToAnswer].shouldDrink = true; | ||
| + | sendMessage(currentToAnswer, MESSAGE_DRINK, players[currentToAnswer].macAdress); | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | } | ||
| + | |||
| + | if (currentState == THUMB_MASTER_STATE) { | ||
| + | // Check if only one remains | ||
| + | if (thumbMasterCount == NUMBER_OF_PLAYERS - 1) { | ||
| + | // Check who it is | ||
| + | for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { | ||
| + | if (!players[i].thumbMaster) { | ||
| + | players[i].shouldDrink = true; | ||
| + | sendMessage(i, MESSAGE_DRINK, players[i].macAdress); | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | currentState = WAITING_FOR_DRINKS_STATE; | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | if (currentState == GAME_OVER_STATE) { | ||
| + | lcdPrint(" GAME OVER", 0, 1); | ||
| + | currentState = IDLE_STATE; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </code> | ||
| + | |||
| + | ==== Cod Player ==== | ||
| + | Asemanator cu Masterul, Playerul este construi tot sub forma unui state-machine: | ||
| + | <code cpp> | ||
| + | #include <esp_now.h> | ||
| + | #include <WiFi.h> | ||
| + | |||
| + | #define THUMB_MASTER_BUTTON_PIN 18 | ||
| + | #define GENERAL_PURPOSE_BUTTON_PIN 19 | ||
| + | #define FORCE_SENSOR_PIN 36 | ||
| + | #define LED_PIN 22 | ||
| + | #define LED2_PIN 17 | ||
| + | #define BUZZER_PIN 23 | ||
| + | |||
| + | #define IDLE_STATE 0 | ||
| + | #define SHOULD_DRINK_STATE 1 | ||
| + | #define SHOULD_SAY_STATE 2 | ||
| + | #define THUMB_MASTER_STATE 3 | ||
| + | #define WATERFALL_STATE 4 | ||
| + | |||
| + | #define MESSAGE_DRINK 0 | ||
| + | #define MESSAGE_CONFIRM 1 | ||
| + | #define MESSAGE_SCREAM 2 | ||
| + | #define MESSAGE_SAY 3 | ||
| + | #define MESSAGE_GO_NEXT 4 | ||
| + | #define MESSAGE_THUMB_MASTER 5 | ||
| + | #define MESSAGE_WATERFALL_GO 6 | ||
| + | #define MESSAGE_WATERFALL_STOP 7 | ||
| + | #define MESSAGE_WATERFALL_GO_INITIATOR 8 | ||
| + | |||
| + | |||
| + | #define GLASS_NOT_LIFTED 0 | ||
| + | #define GLASS_LIFTED 1 | ||
| + | #define GLASS_BACK 2 | ||
| + | |||
| + | typedef struct message_t { | ||
| + | int sender; | ||
| + | int receiver; | ||
| + | int messageType; | ||
| + | } message_t; | ||
| + | |||
| + | // MAC Address of the master ESP | ||
| + | uint8_t broadcastAddress[] = {0xE8, 0x31, 0xCD, 0x14, 0x21, 0x1C}; | ||
| + | |||
| + | // The player ID | ||
| + | int myId = -1; | ||
| + | |||
| + | // Peer info structure | ||
| + | esp_now_peer_info_t peerInfo; | ||
| + | |||
| + | // The drinking flag | ||
| + | int currentState; | ||
| + | |||
| + | // The state of the buzzer | ||
| + | bool buzzerOn = false; | ||
| + | |||
| + | // The state of the glass | ||
| + | int glassState; | ||
| + | |||
| + | // Flag for the waterfall challenge | ||
| + | bool canPutBack; | ||
| + | bool isInitiator; | ||
| + | |||
| + | const int movingAvgLength = 10; | ||
| + | int movingAvgBuffer[movingAvgLength]; | ||
| + | int movingAvgIndex = 0; | ||
| + | |||
| + | // Callback function executed when data is received | ||
| + | void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { | ||
| + | message_t message; | ||
| + | |||
| + | memcpy(&message, incomingData, sizeof(message_t)); | ||
| + | Serial.print("Data received: "); | ||
| + | Serial.println(len); | ||
| + | Serial.print("Sender: "); | ||
| + | Serial.println(message.sender); | ||
| + | Serial.print("Message type: "); | ||
| + | Serial.println(message.messageType == 0 ? "DRINK" : "CONFIRM"); | ||
| + | |||
| + | if (message.messageType == MESSAGE_DRINK) { | ||
| + | currentState = SHOULD_DRINK_STATE; | ||
| + | glassState = GLASS_NOT_LIFTED; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_SCREAM) { | ||
| + | buzzerOn = true; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_SAY) { | ||
| + | currentState = SHOULD_SAY_STATE; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_THUMB_MASTER) { | ||
| + | currentState = THUMB_MASTER_STATE; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_WATERFALL_GO) { | ||
| + | currentState = WATERFALL_STATE; | ||
| + | glassState = GLASS_NOT_LIFTED; | ||
| + | canPutBack = false; | ||
| + | isInitiator = false; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_WATERFALL_GO_INITIATOR) { | ||
| + | currentState = WATERFALL_STATE; | ||
| + | glassState = GLASS_NOT_LIFTED; | ||
| + | canPutBack = false; | ||
| + | isInitiator = true; | ||
| + | } | ||
| + | |||
| + | if (message.messageType == MESSAGE_WATERFALL_STOP) { | ||
| + | canPutBack = true; | ||
| + | } | ||
| + | |||
| + | // Check if it's the first message and update ID | ||
| + | if (myId == -1) { | ||
| + | myId = message.receiver; | ||
| + | } | ||
| + | |||
| + | |||
| + | } | ||
| + | |||
| + | // Callback when data is sent | ||
| + | void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { | ||
| + | char macStr[18]; | ||
| + | Serial.print("Packet to: "); | ||
| + | // Copies the sender mac address to a string | ||
| + | snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", | ||
| + | mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); | ||
| + | Serial.print(macStr); | ||
| + | Serial.print(" send status:\t"); | ||
| + | Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); | ||
| + | } | ||
| + | |||
| + | int calculateMovingAverage(int sensorValue) { | ||
| + | movingAvgBuffer[movingAvgIndex] = sensorValue; | ||
| + | movingAvgIndex = (movingAvgIndex + 1) % movingAvgLength; | ||
| + | |||
| + | int sum = 0; | ||
| + | for (int i = 0; i < movingAvgLength; i++) { | ||
| + | sum += movingAvgBuffer[i]; | ||
| + | } | ||
| + | |||
| + | return sum / movingAvgLength; | ||
| + | } | ||
| + | |||
| + | |||
| + | |||
| + | void setup() { | ||
| + | // Initialize serial communication at 9600 bits per second: | ||
| + | Serial.begin(9600); | ||
| + | |||
| + | // Initialize ESP NOW stuff | ||
| + | // Set ESP32 as a Wi-Fi Station | ||
| + | WiFi.mode(WIFI_STA); | ||
| + | |||
| + | // Initilize ESP-NOW stuff | ||
| + | if (esp_now_init() != ESP_OK) { | ||
| + | Serial.println("Error initializing ESP-NOW"); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // Register callback function | ||
| + | esp_now_register_recv_cb(OnDataRecv); | ||
| + | esp_now_register_send_cb(OnDataSent); | ||
| + | |||
| + | memcpy(peerInfo.peer_addr, broadcastAddress, 6); | ||
| + | if (esp_now_add_peer(&peerInfo) != ESP_OK){ | ||
| + | Serial.println("Failed to add peer"); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | |||
| + | // Initialize the thumb master button | ||
| + | pinMode(THUMB_MASTER_BUTTON_PIN, INPUT_PULLUP); | ||
| + | pinMode(GENERAL_PURPOSE_BUTTON_PIN, INPUT_PULLUP); | ||
| + | // Initialize the led | ||
| + | pinMode(LED_PIN, OUTPUT); | ||
| + | pinMode(LED2_PIN, OUTPUT); | ||
| + | // Initialize the buzzer | ||
| + | pinMode(BUZZER_PIN, OUTPUT); | ||
| + | |||
| + | currentState = IDLE_STATE; | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | if (currentState == SHOULD_DRINK_STATE) { | ||
| + | // Turn on the LED | ||
| + | digitalWrite(LED_PIN, HIGH); | ||
| + | digitalWrite(LED2_PIN, LOW); | ||
| + | |||
| + | if (buzzerOn) { | ||
| + | digitalWrite(BUZZER_PIN, HIGH); | ||
| + | } | ||
| + | |||
| + | // Read the FSR value | ||
| + | int forceSensorValue = calculateMovingAverage(analogRead(FORCE_SENSOR_PIN)); | ||
| + | |||
| + | |||
| + | if ((forceSensorValue <5) && glassState == GLASS_NOT_LIFTED) { | ||
| + | glassState = GLASS_LIFTED; | ||
| + | } | ||
| + | |||
| + | if ((forceSensorValue > 100) && glassState == GLASS_LIFTED) { | ||
| + | glassState = GLASS_BACK; | ||
| + | } | ||
| + | |||
| + | if (glassState == GLASS_BACK) { | ||
| + | // Send the confirmation message | ||
| + | // Create the message | ||
| + | message_t message = {myId, 3, MESSAGE_CONFIRM}; | ||
| + | |||
| + | // Send the message | ||
| + | esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &message, sizeof(message_t)); | ||
| + | |||
| + | // Check the result | ||
| + | if (result == ESP_OK) { | ||
| + | Serial.println("Sent with success"); | ||
| + | currentState = IDLE_STATE; | ||
| + | buzzerOn = false; | ||
| + | } | ||
| + | else { | ||
| + | Serial.println("Error sending the data"); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == SHOULD_SAY_STATE) { | ||
| + | // Turn on the LED | ||
| + | digitalWrite(LED2_PIN, HIGH); | ||
| + | |||
| + | // Wait for the button press | ||
| + | int generalPurposeButtonState = digitalRead(GENERAL_PURPOSE_BUTTON_PIN); | ||
| + | |||
| + | if (!generalPurposeButtonState) { | ||
| + | // Send the message to master to go to the next player | ||
| + | message_t message = {myId, 3, MESSAGE_GO_NEXT}; | ||
| + | esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &message, sizeof(message_t)); | ||
| + | if (result == ESP_OK) { | ||
| + | Serial.println("Sent with success"); | ||
| + | currentState = IDLE_STATE; | ||
| + | buzzerOn = false; | ||
| + | } | ||
| + | else { | ||
| + | Serial.println("Error sending the data"); | ||
| + | } | ||
| + | digitalWrite(LED2_PIN, LOW); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == THUMB_MASTER_STATE) { | ||
| + | int thumbMasterButtonState = digitalRead(THUMB_MASTER_BUTTON_PIN); | ||
| + | if (!thumbMasterButtonState) { | ||
| + | message_t message = {myId, 3, MESSAGE_THUMB_MASTER}; | ||
| + | esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &message, sizeof(message_t)); | ||
| + | if (result == ESP_OK) { | ||
| + | Serial.println("Sent with success"); | ||
| + | currentState = IDLE_STATE; | ||
| + | buzzerOn = false; | ||
| + | } | ||
| + | else { | ||
| + | Serial.println("Error sending the data"); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == WATERFALL_STATE) { | ||
| + | int forceSensorValue = calculateMovingAverage(analogRead(FORCE_SENSOR_PIN)); | ||
| + | if (forceSensorValue <5 && (glassState == GLASS_NOT_LIFTED || glassState == GLASS_BACK)) { | ||
| + | glassState = GLASS_LIFTED; | ||
| + | } | ||
| + | |||
| + | if (forceSensorValue > 5 && glassState == GLASS_LIFTED) { | ||
| + | glassState = GLASS_BACK; | ||
| + | if (isInitiator) { | ||
| + | canPutBack = true; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if ((glassState == GLASS_NOT_LIFTED || glassState == GLASS_BACK) && !canPutBack) { | ||
| + | // Turn on the alert | ||
| + | digitalWrite(LED_PIN, HIGH); | ||
| + | digitalWrite(BUZZER_PIN, HIGH); | ||
| + | } | ||
| + | |||
| + | if (glassState == GLASS_LIFTED && !canPutBack) { | ||
| + | digitalWrite(LED_PIN, LOW); | ||
| + | digitalWrite(BUZZER_PIN, LOW); | ||
| + | } | ||
| + | |||
| + | if (glassState == GLASS_BACK && canPutBack) { | ||
| + | message_t message = {myId, 3, MESSAGE_WATERFALL_STOP}; | ||
| + | esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &message, sizeof(message_t)); | ||
| + | if (result == ESP_OK) { | ||
| + | Serial.println("Sent with success"); | ||
| + | currentState = IDLE_STATE; | ||
| + | buzzerOn = false; | ||
| + | } | ||
| + | else { | ||
| + | Serial.println("Error sending the data"); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (currentState == IDLE_STATE) { | ||
| + | // Turn off the LED | ||
| + | digitalWrite(LED_PIN, LOW); | ||
| + | digitalWrite(BUZZER_PIN, LOW); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </code> | ||
| ===== Rezultate Obţinute ===== | ===== Rezultate Obţinute ===== | ||
| + | Modulul Master: | ||
| - | <note tip> | + | {{:pm:prj2023:adarmaz:modulmasterkingspoza.jpeg?300|}} |
| - | Care au fost rezultatele obţinute în urma realizării proiectului vostru. | + | |
| - | </note> | + | |
| - | ===== Concluzii ===== | + | Modul Player: |
| - | ===== Download ===== | + | {{:pm:prj2023:adarmaz:modulplayerkingspoza.jpeg?300|}} |
| - | <note warning> | + | Video cu flow-ul jocului: |
| - | O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-). | + | |
| - | Fişierele se încarcă pe wiki folosind facilitatea **Add Images or other files**. Namespace-ul în care se încarcă fişierele este de tipul **:pm:prj20??:c?** sau **:pm:prj20??:c?:nume_student** (dacă este cazul). **Exemplu:** Dumitru Alin, 331CC -> **:pm:prj2009:cc:dumitru_alin**. | + | <html> |
| - | </note> | + | <iframe width="560" height="315" src="https://www.youtube.com/embed/KnaIk49ovkk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></html> |
| + | ===== Concluzii ===== | ||
| + | Obiectivul meu in cadrul proiectului a fost sa gasesc o idee noua, si sa o implementez intr-un mod care sa ma invete ceva nou. Consider ca am atins ambele obiective. Nu am putut gasi pe internet ceva asemanator, ceea ce a fost in acelasi timp satisfacator si challenging, pentru ca nu am avut nicio varianta de fallback. Cel mai important lucru pe care l-am invatat este functionalitatea bibliotecii ESP-NOW. | ||
| + | Cel mai mare challenge are legatura cu faptul ca proiectul este alcatuit din multe module, ceea ce a facut testarea destul de dificila. Pe partea de cod, implementarea initiala a comunicarii si a unei provocari de test a fost cea mai interesanta, urmand ca restul de functionalitati sa fie usor de implementat, fiind foarte asemanatoare cu prima. | ||
| + | |||
| + | ===== Download ===== | ||
| + | Link Github: https://github.com/mihneabranzeu/King-s-Cup-Arduino | ||
| ===== Jurnal ===== | ===== Jurnal ===== | ||
| * 5 Mai - Am creat pagina de documentatie | * 5 Mai - Am creat pagina de documentatie | ||
| + | * 6 Mai - Am comandat piesele | ||
| + | * 10 Mai - Au ajuns piesele | ||
| + | * 20 - 23 Mai - Am lucrat la proiect | ||
| + | * 24 Mai - Am prezentat proiectul la laborator | ||
| + | * 30 Mai - PM Fair | ||
| ===== Bibliografie/Resurse ===== | ===== Bibliografie/Resurse ===== | ||
| - | + | * https://www.youtube.com/watch?v=bEKjCDDUPaU | |
| - | <note> | + | * https://dronebotworkshop.com/esp-now/ |
| - | Listă cu documente, datasheet-uri, resurse Internet folosite, eventual grupate pe **Resurse Software** şi **Resurse Hardware**. | + | |
| - | </note> | + | |
| - | + | ||
| - | <html><a class="media mediafile mf_pdf" href="?do=export_pdf">Export to PDF</a></html> | + | |