// Author : Quentin BENETHUILLERE // Date of creation : 2021/05/01 // Last modification : 2021/05/01 /* * Program function(s): * This program is the MAIN program that controls the puzzle box that I designed for the FabAcademy program 2021. * It handles the following functions : * - Communication with a LCD using i2c protocol. * - Communication with 2 RFID MFR522 card readers using SPI protocol. * - Communication with a BT module HC-06 through UART. * - Control of 2 separate solenoid lock mechanisms. * - Control of a servo motor that acts also as a lock mechanism. * - Detection of user inputs on 4 or 5 push buttons. * * Detailed explanations and expected scenario: * - 1: Players solve puzzle 1 (handled by another PCB) => Pin puzzle 1 goes HIGH and thus solenoid 1 gets energized (opens). Pin puzzle 1 goes back to LOW after a few seconds. * - 2: Players solve puzzle 2 (handled by another PCB) => Pin puzzle 2 goes HIGH and thus solenoid 2 gets energized (opens). Pin puzzle 2 goes back to LOW after a few seconds. * - 3: Players solve a mechanical puzzle that allow to go further in the game. * - 4: At this step only, players have in their hands the 2 RFID tags to solve next puzzle. * - If a correct RFID tag is detected by one of the reader, RGB LED blinks (blue). * - If a wrong RFID tag is detected by one of the reader, the RGB LED turns ON red for a few seconds. * - If both RFID tags are presented successively within a certain amount of time to their corresponding readers, RGB LED turns ON green for a few seconds. * - The sequence is reset after a wrong RFID identification or following a certain time without any RFID tag detected. * - 5: Once the puzzle described in the previous step is solved, LCD is ON and lets user solve the last puzzle which corresponds to a DNA sequence to enter using a dedicated keypad with 4 buttons : A/C/T/G. * - Each user input on the keypad is displayed on the LCD. * - If the correct sequence is entered, LCD displays an access authorization message, and the servomotor rotates to allow players to open final door. * - If a wrong sequence is entered, LCD displays an access denied message, and the servomotor remains in its position, holding the door locked. * */ //* ----------- //* LIBRAIRIES //* ----------- #include // Library for debouncing push buttons. #include // Library for LCD using i2c protocol. #include // Library for i2c communication. #include // Library for servo motor. #include // Library for SPI protocol. #include // Library for RFID modules. /*Notes: - PICC (Proximity Integrated Circuit Card) defines the rfid card. - PCD (Proximity Coupling Device) defines the MFRC522 card reader. */ // ------------------------- // DECLARATION OF DEFINES // ------------------------- // Defining colors association for the RGB LED : #define RGB_LED_OFF 0 #define RED 1 #define GREEN 2 #define BLUE 3 //#define COMMON_ANODE // //uncomment this line ONLY if using a Common Anode RGB LED. // ------------------------- // DECLARATION OF VARIABLES // ------------------------- // ------------------------------------------------ // Adjustable variables (program main parameters) // ------------------------------------------------ // DNA puzzle : const int DNA_code_length = 8; //char DNA_code[DNA_code_length+1]="CTGGATACCAAGTACG"; // DNA sequence code to be found. [17] because character '\0' added at the end of a string input, and this avoid confusions when string comparison will be performed later. Note : +1 to account for the '\0' end character. //char DNA_code[DNA_code_length+1]="AAAAAAAAAAAAAAAA"; // For testing purposes ONLY. Note : +1 to account for the '\0' end character. char DNA_code[DNA_code_length+1]="ATCGGCTA"; // For testing purposes ONLY. Note : +1 to account for the '\0' end character. // Warning : Do not set the "DNA_code" variable as "const" otherwise the minicore ATmega328 returns an error at compilation. Which is not the case when using "Arduino Pro Mini" board... const int debounce_delay = 5; // Delay used to debounce buttons (if button state change has been observed for this consecutive delay at least, then the state change is confirmed). // RFID puzzle : const int TIMER_rfid_ok = 3000; // Duration (ms) during which the green LED will be turned ON after authorized access. const int TIMER_rfid_ko = 2000; // Duration (ms) during which the red LED will be turned ON after denied access. const int TIMER_rfid_partially_OK = 500; // Duration (ms) during which the blue LED will be turned ON after only 1 of the 2 cards needed for access authorization is detected. const int TIMER_clearing_uid = 10000; // Duration (ms) during which the uid of a card/tag is saved in the memory before being cleared. const int nb_tags_authorized = 1; // Number of tags/cards authorized. // Warning : necesseray to declare this object as "const int" otherwise an error message is raised. byte authorized_tags_1[nb_tags_authorized][4]={{0x8A, 0x37, 0xF2, 0x80}}; // Array of authorized uid for reader 1 (note : 1 uid = 4 bytes given in HEX). Syntax for several uid : {{0x77, 0x6F, 0xB3, 0x85},{0x9A, 0x27, 0x0A, 0x86}} byte authorized_tags_2[nb_tags_authorized][4]={{0x9A, 0x27, 0x0A, 0x86}}; // Array of authorized uid for reader 2 (note : 1 uid = 4 bytes given in HEX). Syntax for several uid : {{0x77, 0x6F, 0xB3, 0x85},{0x9A, 0x27, 0x0A, 0x86}} // Global : const int TIMER_scrolling_text = 300; // Duration between each step of LCD text scrolling. const int TIMER_scrolling_text_start = 1500; // Duration between each step of LCD text scrolling. const int TIMER_fixed_text = 3000; // Duration for which a help message is displayed if its length is <16. const int max_message_length = 140; const char *message_1 = {"Help message for puzzle 1"}; //{"Covid 19 pandemic phases"}; //{"Remember the successive phases we lived during the Covid 19 pandemic."}; const char *message_2 = {"Help message for puzzle 2"}; //{"Covid 19 spreading"}; //{"Pandemic progressed really fast around the world, reconstitute its spreading."}; const char *message_3 = {"Help message for puzzle 3"}; //{"Serveral viruses found?"}; //{"How many viruses have you found so far?"}; // {"How many viruses have you found so far? Look for a date to solve mechanical puzzle. Any similarity between viruses?"}; const char *message_4 = {"Help message for puzzle 4"}; //{"Virus common sequence"}; //{"Think as a scientific who wants to develop a vaccine."}; // ------------------------------------------------ // PIN connections // ------------------------------------------------ // Digital const byte PIN_countdown = 4; // Pin to communicate with the countdown board. const byte PIN_button_A = 20; // Pin corresponding to button "A" of the DNA keypad. const byte PIN_button_T = 21; // Pin corresponding to button "T" of the DNA keypad. const byte PIN_button_C = 7; // Pin corresponding to button "C" of the DNA keypad. const byte PIN_button_G = 8; // Pin corresponding to button "G" of the DNA keypad. const byte PIN_button_H = 10; // Pin corresponding either to a "Erase" button or to a "Help" button to be integrated later on. const byte PIN_buttons[4]={PIN_button_A,PIN_button_C,PIN_button_T,PIN_button_G}; // array of buttons pins for faciliating programming. const byte PIN_puzzle_1 = 17; // Pin corresponding to the feedback from another PCB related to puzzle 1 status. const byte PIN_puzzle_2 = 2; // Pin corresponding to the feedback from another PCB related to puzzle 2 status. //* Analogic PIN : // N/A // SPI : const byte PIN_MOSI = 11; const byte PIN_MISO = 12; const byte PIN_SCK = 13; const byte PIN_SS_1 = 14; // Pin for Slave Selection of RFID 1. const byte PIN_SS_2 = 15; // Pin for Slave Selection of RFID 2. const byte PIN_RST = 16; // I2C : const byte PIN_SDA = 17; const byte PIN_SCL = 18; // RX/TX : const byte RX = 0; const byte TX = 1; // PWM : const byte PIN_servo = 3; // Pin associated to the servo motor const byte PIN_RGB_LED_red = 5; // Pin associated to the red leg of the RGB LED. const byte PIN_RGB_LED_green = 6; // Pin associated to the green leg of the RGB LED. const byte PIN_RGB_LED_blue = 9; // Pin associated to the blue leg of the RGB LED. // ------------------------------------------------ // Other variables // ------------------------------------------------ // DNA puzzle: const char buttons_letters[5]={'A','C','T','G','_'}; // array of the letters associated to each push button input on the DNA keypad. char lcd_line_0[17] = "Access Code?"; // Message to be displayed on the first line of the LCD. //char lcd_line_1[DNA_code_length+1] = "________"; // Message to be displayed on the second line of the LCD. char lcd_line_1[DNA_code_length+1]; byte button_A_state = 0; // variable to know whether button A is pushed (0) or not (1). byte button_C_state = 0; // variable to know whether button C is pushed (0) or not (1). byte button_T_state = 0; // variable to know whether button T is pushed (0) or not (1). byte button_G_state = 0; // variable to know whether button G is pushed (0) or not (1). byte input_position = 0; // variable to know the position of next user input in the DNA sequence (corresponds basically to the column position the next input will be displayed in the LCD). // RFID puzzle : byte rfid_uid_1[4]; // Table containing the uid of the rfid tag/card read by the first card reader (table of 4 bytes). byte rfid_uid_2[4]; // Table containing the uid of the rfid tag/card read by the second card reader (table of 4 bytes). byte access_authorized=0; // Variable that determines whether the access is authorized (1) or denied (0). int card_detected_1 = 0; // Variable to determine whether a tag/card has been detected in the proximity of first reader. int card_detected_2 = 0; // Variable to determine whether a tag/card has been detected in the proximity of second reader. int card_read_1 = 0; // Variable to determine whether a tag/card has been identified and selected by the first reader. int card_read_2 = 0; // Variable to determine whether a tag/card has been identified and selected by the second reader. long t_card_read_1=0; // Variable to determine the last time when a card/tag has been selected by the first reader. This variable will be used to clear from the memory the uid read after a certain time. long t_card_read_2=0; // Variable to determine the last time when a card/tag has been selected by the second reader. This variable will be used to clear from the memory the uid read after a certain time. byte uid_erased_1=0; byte uid_erased_2=0; // Global : byte button_H_state = 0; // variable to know whether button Erase is pushed (0) or not (1). byte i2c_address_LCD = 0x27; // address to communicate with the LCD over i2c (Note : "0x27" by default, "0x26" if A0 and A1 are connected on the LCD i2c module) byte servo_locked = 30; // angle that defines the servo motor position to lock the door. byte servo_open = 120; // angle that defines the servo motor position to open the door. byte game_progress = 1; // variable to determine at which step of the game players are. This may be needed to provide different help messages for each step of the game. byte end_game = 5; // variable to determine for which value of "game_progress" the game is completed. byte puzzle_1_status = 0; byte puzzle_2_status = 0; long last_help_time = 0; // time when help was requested for the last time (used to control when a help message must be stopped). byte lcd_cursor_scrolling = 0; // Variable to control scrolling text on the LCD. byte countdown_started = 0; // ------------------------- // DECLARATION OF OBJECTS // ------------------------- LiquidCrystal_I2C lcd(i2c_address_LCD, 16, 2);// Declaration of the i2c LCD (16 columns, 2 rows). Bounce2::Button button_A; // Declaration of push button A. Bounce2::Button button_C; // Declaration of push button C. Bounce2::Button button_T; // Declaration of push button T. Bounce2::Button button_G; // Declaration of push button G. //Bounce2::Button button_H; // Declaration of push button H (help). Bounce2::Button buttons[4]={button_A,button_C,button_T,button_G}; // Declaration of an array of buttons for code optimization. Servo servo; // Declaration of the servo motor. MFRC522 rfid_1(PIN_SS_1, PIN_RST); // Declaration of a MFRC522 RFID card reader 1. MFRC522 rfid_2(PIN_SS_2, PIN_RST); // Declaration of a MFRC522 RFID card reader 2. // ------------------------- // INITIALIZATION // ------------------------- // Setup code. Run once at power up, or each time the RESET button is pressed: void setup() { // ------------------------------------------------ // Communication // ------------------------------------------------ //Serial.begin(9600); // Establishement of Serial communication : SPI.begin(); // Establishment of SPI (Serial Peripheral Inteface) communication : // ------------------------------------------------ // Objects // ------------------------------------------------ // Initialization of the LCD object (turned off in the beginning) : //lcd.begin(16,2); lcd.init(); lcd.noBacklight(); lcd.clear(); // Initialization of push buttons objects, with debounce delay and state when pressed : for (unsigned char i=0; i<4; i++){ buttons[i].attach(PIN_buttons[i]); buttons[i].interval(debounce_delay); buttons[i].setPressedState(LOW); } // Initialization of servo motor object : servo.attach(PIN_servo); servo.write(servo_locked); // setting servo motor so that door is locked : // Initialization of RFID readers: rfid_1.PCD_Init(); // Initialization of the MFRC522 object : rfid_2.PCD_Init(); // Initialization of the MFRC522 object : // ------------------------------------------------ // Input / Output // ------------------------------------------------ pinMode(PIN_countdown, OUTPUT); pinMode(PIN_SS_1, OUTPUT); pinMode(PIN_SS_2, OUTPUT); pinMode(PIN_RGB_LED_red, OUTPUT); pinMode(PIN_RGB_LED_green, OUTPUT); pinMode(PIN_RGB_LED_blue, OUTPUT); pinMode(PIN_puzzle_1, INPUT); // HW pull-down resistor is present in the PCB. pinMode(PIN_puzzle_2, INPUT); // HW pull-down resistor is present in the PCB. for (byte i=0; i<=4; i++){ pinMode(PIN_buttons[i],INPUT_PULLUP); } // ------------------------------------------------ // PIN initial states // ------------------------------------------------ digitalWrite(PIN_countdown,HIGH); // Coutdown should not start immediately at initialization. LOW = countdown running / HIGH = countdown not running. analogWrite(PIN_RGB_LED_red,0); // RGB LED OFF. analogWrite(PIN_RGB_LED_green,0); // RGB LED OFF. analogWrite(PIN_RGB_LED_blue,0); // RGB LED OFF. for (int i=0; i= TIMER_clearing_uid) && uid_erased_1==0){ for (byte i=0;i<4;i++){ rfid_uid_1[i]=0; } // end for uid_erased_1 = 1; //Serial.println("uid from reader 1 erased"); } // end if if (((millis()-t_card_read_2) >= TIMER_clearing_uid) && uid_erased_2==0){ for (byte i=0;i<4;i++){ rfid_uid_2[i]=0; } // end for //Serial.println("uid from reader 2 erased"); uid_erased_2 = 1; } // end if card_detected_1 = rfid_1.PICC_IsNewCardPresent(); card_detected_2 = rfid_2.PICC_IsNewCardPresent(); card_read_1 = rfid_1.PICC_ReadCardSerial(); card_read_2 = rfid_2.PICC_ReadCardSerial(); // If no card/tag is detected around the card reader, the program does not go any further and the whole loop is repeated. if (!(card_detected_1 || card_detected_2)){ lcd.setCursor(0,1); lcd.print("No card"); //Serial.println("No card/tag detected"); return; } // end if // If no card/tag has been selected after the authentication process, the program does not go any further and the whole loop is repeated. if (!(card_read_1 || card_read_2)){ //Serial.println("No card/tag selected"); lcd.setCursor(0,1); lcd.print("No selection"); return; } // end if // If card identified and selected by the first reader : if (card_read_1){ lcd.setCursor(0,1); lcd.print("Reader 1"); // Reading the uid of the selected tag/card (composed of 4 bytes) : for (byte i = 0; i < 4; i++){ rfid_uid_1[i] = rfid_1.uid.uidByte[i]; } // end for // Displaying the card/tag uid on the Serial line : //display_uid(rfid_uid_1,1); uid_erased_1=0; // Reinitializing the RFID : rfid_1.PICC_HaltA(); // Halting PICC (making it go from "Selected" mode to "Halt" mode). rfid_1.PCD_StopCrypto1(); // Stopping encryption on PCD. // Saving the authentication time from reader 1: t_card_read_1=millis(); } // end if // If card identified and selected by the second reader : if (card_read_2){ lcd.setCursor(0,1); lcd.print("Reader 2"); // Reading the uid of the selected tag/card (composed of 4 bytes) : for (byte i = 0; i < 4; i++){ rfid_uid_2[i] = rfid_2.uid.uidByte[i]; } // end for // Displaying the card/tag uid on the Serial line : //display_uid(rfid_uid_2,2); uid_erased_2=0; // Reinitializing the RFID : rfid_2.PICC_HaltA(); // Halting PICC (making it go from "Selected" mode to "Halt" mode). rfid_2.PCD_StopCrypto1(); // Stopping encryption on PCD. // Saving the authentication time from reader 2: t_card_read_2=millis(); } // end if // Controlling Access : access_authorized=RFID_access_control(authorized_tags_1,authorized_tags_2,rfid_uid_1,rfid_uid_2); // if the access is denied (a card/tag non authorized by a specific reader has been identified) : if (access_authorized==0){ set_color_by_name(RED); //Serial.println("Access denied !"); delay(TIMER_rfid_ko); set_color_by_name(RGB_LED_OFF); } // end if // if the access is authorized (both cards/tags have been identified simultaneously by the corresponding readers) : else if (access_authorized==1){ set_color_by_name(GREEN); //Serial.println("Access authorized !"); delay(TIMER_rfid_ok); set_color_by_name(RGB_LED_OFF); if (game_progress==3){ game_progress = game_progress+1; } // end if } // end else if // if only one of the cards/tags has been identified by the appropriate reader, access is not authorized yet, but the orange LED indicates that something is happening : else if (access_authorized==2){ set_color_by_name(BLUE); //Serial.println("One tag/card identified, present the second tag simultaneously for access authorization !"); delay(TIMER_rfid_partially_OK); set_color_by_name(RGB_LED_OFF); } // end else if } // end "RFID_puzzle" function void RFID_puzzle_one_reader(){ //lcd.setCursor(0,1); //lcd.print("RFID puzzle"); if (((millis()-t_card_read_1) >= TIMER_clearing_uid) && uid_erased_1==0){ for (byte i=0;i<4;i++){ rfid_uid_1[i]=0; } // end for uid_erased_1 = 1; //Serial.println("uid from reader 1 erased"); } // end if card_detected_1 = rfid_1.PICC_IsNewCardPresent(); card_read_1 = rfid_1.PICC_ReadCardSerial(); // If no card/tag is detected around the card reader, the program does not go any further and the whole loop is repeated. if (!(card_detected_1)){ //lcd.setCursor(0,1); //lcd.print("No card"); //Serial.println("No card/tag detected"); return; } // end if // If no card/tag has been selected after the authentication process, the program does not go any further and the whole loop is repeated. if (!(card_read_1)){ //Serial.println("No card/tag selected"); //lcd.setCursor(0,1); //lcd.print("No selection"); return; } // end if // If card identified and selected by the reader : //lcd.setCursor(0,1); //lcd.print("Reader 1"); // Reading the uid of the selected tag/card (composed of 4 bytes) : for (byte i = 0; i < 4; i++){ rfid_uid_1[i] = rfid_1.uid.uidByte[i]; } // end for // Displaying the card/tag uid on the Serial line : //display_uid(rfid_uid_1,1); uid_erased_1=0; // Reinitializing the RFID : rfid_1.PICC_HaltA(); // Halting PICC (making it go from "Selected" mode to "Halt" mode). rfid_1.PCD_StopCrypto1(); // Stopping encryption on PCD. // Saving the authentication time from reader 1: t_card_read_1=millis(); // Controlling Access : access_authorized=RFID_access_control_one_reader(authorized_tags_1,rfid_uid_1); // if the access is denied (a card/tag non authorized by a specific reader has been identified) : if (access_authorized==0){ set_color_by_name(RED); //Serial.println("Access denied !"); delay(TIMER_rfid_ko); set_color_by_name(RGB_LED_OFF); } // end if // if the access is authorized (both cards/tags have been identified simultaneously by the corresponding readers) : else if (access_authorized==1){ set_color_by_name(GREEN); //Serial.println("Access authorized !"); delay(TIMER_rfid_ok); set_color_by_name(RGB_LED_OFF); if (game_progress==3){ game_progress = game_progress+1; } // end if } // end else if // if only one of the cards/tags has been identified by the appropriate reader, access is not authorized yet, but the orange LED indicates that something is happening : else if (access_authorized==2){ set_color_by_name(BLUE); //Serial.println("One tag/card identified, present the second tag simultaneously for access authorization !"); delay(TIMER_rfid_partially_OK); set_color_by_name(RGB_LED_OFF); } // end else if } // end "RFID_puzzle" function byte RFID_access_control(byte tags_list_1[nb_tags_authorized][4], byte tags_list_2[nb_tags_authorized][4], byte uid_controlled_1[4],byte uid_controlled_2[4]) { // This functions determines whether access authorization can be given or not, based on the status of the 2 card readers. byte rfid_access_1=0; byte rfid_access_2=0; byte access=0; //access authorized (1), access denied (0) for (byte i=0;i