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