|
//Code Breaker |
|
//Source Michael Klements Arduino Uno code |
|
//Converted to ESP32 - Asaf Matan |
|
|
|
#include <SPI.h> //Import libraries to control the OLED display |
|
#include <Wire.h> |
|
#include <Adafruit_GFX.h> |
|
#include <Adafruit_SSD1306.h> |
|
#include <Servo.h> //Import library to control the servo |
|
#include "AiEsp32RotaryEncoder.h" |
|
|
|
#define SCREEN_WIDTH 128 // OLED display width, in pixels |
|
#define SCREEN_HEIGHT 64 // OLED display height, in pixels |
|
#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 |
|
|
|
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin) |
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) |
|
Servo lockServo; // create servo object to control a servo |
|
|
|
#define ROTARY_ENCODER_A_PIN 5 |
|
#define ROTARY_ENCODER_B_PIN 15 |
|
#define ROTARY_ENCODER_BUTTON_PIN 23 |
|
#define ROTARY_ENCODER_STEPS 4 |
|
|
|
byte correctNumLEDs[4] = {27,14,33,18}; //Pin numbers for correct number LEDs (Indicate a correct digit) |
|
byte correctPlaceLEDs[4] = {12,26,25,32}; //Pin numbers for correct place LEDs (Indicate a correct digit in the correct place) |
|
|
|
byte code[4] = {0,0,0,0}; //Create an array to store the code digits |
|
byte codeGuess[4] = {0,0,0,0}; //Create an array to store the guessed code digits |
|
byte guessingDigit = 0; //Tracks the current digit being guessed |
|
byte numGuesses = 0; //Tracks how many guesses it takes to crack the code |
|
boolean correctGuess = true; //Variable to check whether the code has been guessed correctly, true initially to generate a new code on startup |
|
byte encoderPos = 0; //Current value of encoder position, digit being input form 0 to 9 |
|
|
|
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, -1, ROTARY_ENCODER_STEPS); |
|
int buttonState = 0; |
|
void IRAM_ATTR readEncoderISR() |
|
{ |
|
rotaryEncoder.readEncoder_ISR(); |
|
} |
|
|
|
|
|
/* Local Functions decleration */ |
|
void startupAni (); |
|
void updateDisplayCode(); |
|
void generateNewCode(); |
|
void inputCodeGuess(); |
|
void checkCodeGuess(); |
|
void updateLEDs (int corNum, int corPla); |
|
|
|
void setup() |
|
{ |
|
Serial.begin(115200); //Starts the Serial monitor for debugging |
|
Serial.println(F("Starting...")); |
|
rotaryEncoder.begin(); |
|
rotaryEncoder.setup(readEncoderISR); |
|
rotaryEncoder.setBoundaries(0, 9, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa) |
|
rotaryEncoder.setAcceleration(250); |
|
// Let Oled diplay power up |
|
delay(3000); |
|
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { |
|
Serial.println(F("SSD1306 allocation failed")); |
|
for(;;); // Don't proceed, loop forever |
|
} |
|
display.clearDisplay(); //Clear display |
|
display.setTextColor(SSD1306_WHITE); //Set the text colour to white |
|
|
|
lockServo.attach(19); |
|
for(int i=0 ; i<=3 ; i++) //Define pin modes for the LEDs |
|
{ |
|
pinMode(correctNumLEDs[i], OUTPUT); |
|
pinMode(correctPlaceLEDs[i], OUTPUT); |
|
digitalWrite(correctPlaceLEDs[i], LOW); |
|
digitalWrite(correctNumLEDs[i], LOW); |
|
} |
|
|
|
pinMode(ROTARY_ENCODER_A_PIN, INPUT_PULLUP); //Set pinA as an input, pulled HIGH to the logic voltage |
|
pinMode(ROTARY_ENCODER_B_PIN, INPUT_PULLUP); //Set pinB as an input, pulled HIGH to the logic voltage |
|
delay(2000); // Pause for 2 seconds |
|
|
|
startupAni(); //Display the startup animation |
|
} |
|
|
|
void loop() |
|
{ |
|
if(correctGuess) //Code between games to reset if the guess is correct, initially true to open safe and then generate new code |
|
{ |
|
lockServo.write(45); //Unlock the safe |
|
delay(300); |
|
updateLEDs (0,4); //Flashing LED sequence |
|
delay(300); |
|
updateLEDs (4,0); |
|
delay(300); |
|
updateLEDs (0,4); |
|
delay(300); |
|
updateLEDs (4,0); |
|
delay(300); |
|
updateLEDs (4,4); //Turn all LEDs on |
|
if(numGuesses >= 1) //Check that its not the start of the game |
|
{ |
|
display.clearDisplay(); //Clear the display |
|
display.setTextSize(1); //Set the display text size to small |
|
display.setCursor(35,10); //Set the display cursor position |
|
display.print(F("In ")); //Set the display text |
|
display.print(numGuesses); //Set the display text |
|
display.setCursor(35,20); //Set the display cursor position |
|
display.print(F("Attempts")); //Set the display text |
|
display.display(); //Output the display text |
|
delay(5000); |
|
} |
|
display.clearDisplay(); //Clear the display |
|
display.setTextSize(1); //Set the display text size to small |
|
display.setCursor(35,10); //Set the display cursor position |
|
display.print(F("Push To")); //Set the display text |
|
display.setCursor(35,20); //Set the display cursor position |
|
display.print(F("Lock Safe")); //Set the display text |
|
display.display(); //Output the display text |
|
display.setTextSize(2); //Set the display text size back to large |
|
boolean lock = false; //Safe is initially not locked |
|
while(!lock) //While button is not pressed, wait for it to be pressed |
|
{ |
|
if (rotaryEncoder.isEncoderButtonClicked()) |
|
{ |
|
Serial.println("button pressed"); |
|
lockServo.write(140); //Lock the safe |
|
display.clearDisplay(); //Clear the display |
|
display.setCursor(30,10); //Set the display cursor position |
|
display.print(F("Locked")); //Set the display text |
|
display.display(); //Output the display text |
|
lock = true; |
|
} |
|
} |
|
generateNewCode(); //Calls function to generate a new random code |
|
updateLEDs (0,0); |
|
correctGuess = false; //The code guess is initially set to incorrect |
|
numGuesses = 0; //Reset the number of guesses counter |
|
} |
|
inputCodeGuess(); //Calls function to allow the user to input a guess |
|
numGuesses++; //Increment the guess counter |
|
checkCodeGuess(); //Calls function to check the input guess |
|
guessingDigit = 0; //Reset the digit being guessed |
|
codeGuess[0] = 0; //Reset the first digit of the code |
|
updateDisplayCode(); //Update the displayed code |
|
} |
|
|
|
void updateDisplayCode() //Function to update the display with the input code |
|
{ |
|
String temp = ""; //Temporary variable to concatenate the code string |
|
if(!correctGuess) //If the guess is not correct then update the display |
|
{ |
|
for (int i=0 ; i<guessingDigit ; i++) //Loops through the four digits to display them |
|
{ |
|
temp = temp + codeGuess[i]; |
|
} |
|
temp = temp + encoderPos; |
|
for (int i=guessingDigit+1 ; i<=3 ; i++) |
|
{ |
|
temp = temp + "0"; |
|
} |
|
Serial.println(temp); //Output to Serial monitor for debugging |
|
display.setTextSize(2); //Set the display text size |
|
display.clearDisplay(); //Clear the display |
|
display.setCursor(40,10); //Set the display cursor position |
|
display.println(temp); //Set the display text |
|
display.display(); //Update the display |
|
} |
|
} |
|
|
|
void generateNewCode() //Function to generate a new random code |
|
{ |
|
Serial.print("Code: "); |
|
|
|
code[0]=0; |
|
code[1]=5; |
|
code[2]=0; |
|
code[3]=2; |
|
for (int i=0 ; i<= 3 ; i++) //Loops through the four digits and assigns a random number to each |
|
{ |
|
// esp_random(); |
|
// code[i] = random(0,9); //Generate a random number for each digit |
|
Serial.print(code[i]); //Display the code on Serial monitor for debugging |
|
} |
|
Serial.println(); |
|
} |
|
|
|
void inputCodeGuess() //Function to allow the user to input a guess |
|
{ |
|
for(int i=0 ; i<=3 ; i++) //User must guess all four digits |
|
{ |
|
guessingDigit = i; |
|
boolean confirmed = false; //Both used to confirm button push to assign a digit to the guess code |
|
//rotaryEncoder.setEncoderValue(0); |
|
updateDisplayCode(); |
|
while(!confirmed) //While the user has not confirmed the digit input |
|
|
|
{ |
|
if (rotaryEncoder.encoderChanged()) |
|
{ |
|
Serial.println(rotaryEncoder.readEncoder()); |
|
encoderPos = rotaryEncoder.readEncoder(); //Store encoder position |
|
codeGuess[i] = encoderPos; //If the button is pressed, accept the current digit into the guessed code |
|
updateDisplayCode(); |
|
} |
|
|
|
if (rotaryEncoder.isEncoderButtonClicked()) |
|
{ |
|
codeGuess[i] = encoderPos; //If the button is pressed, accept the current digit into the guessed code |
|
updateDisplayCode(); |
|
confirmed=true; //Update the code being displayed |
|
} |
|
} |
|
} |
|
} |
|
|
|
void checkCodeGuess() //Function to check the users guess against the generated code |
|
{ |
|
int correctNum = 0; //Variable for the number of correct digits in the wrong place |
|
int correctPlace = 0; //Variable for the number of correct digits in the correct place |
|
int usedDigits[4] = {0,0,0,0}; //Mark off digits which have been already identified in the wrong place, avoids counting repeated digits twice |
|
for (int i=0 ; i<= 3 ; i++) //Loop through the four digits in the guessed code |
|
{ |
|
for (int j=0 ; j<=3 ; j++) //Loop through the four digits in the generated code |
|
{ |
|
if (codeGuess[i]==code[j]) //If a number is found to match |
|
{ |
|
if(usedDigits[j]!=1) //Check that it hasn't been previously identified |
|
{ |
|
correctNum++; //Increment the correct digits in the wrong place counter |
|
usedDigits[j] = 1; //Mark off the digit as been identified |
|
break; //Stop looking once the digit is found |
|
} |
|
} |
|
} |
|
} |
|
for (int i=0 ; i<= 3 ; i++) //Compares the guess digits to the code digits for correct digits in correct place |
|
{ |
|
if (codeGuess[i]==code[i]) //If a correct digit in the correct place is found |
|
correctPlace++; //Increment the correct place counter |
|
} |
|
Serial.print("Correct Place: "); |
|
Serial.println(correctPlace); |
|
updateLEDs(correctNum, correctPlace); //Calls a function to update the LEDs to reflect the guess |
|
if(correctPlace==4) //If all 4 digits are correct then the code has been cracked |
|
{ |
|
display.clearDisplay(); //Clear the display |
|
display.setCursor(20,10); //Set the display cursor position |
|
display.print(F("Cracked")); //Set the display text |
|
display.display(); //Output the display text |
|
correctGuess = true; |
|
} |
|
else |
|
correctGuess = false; |
|
} |
|
|
|
void updateLEDs (int corNum, int corPla) //Function to update the LEDs to reflect the guess |
|
{ |
|
for(int i=0 ; i<=3 ; i++) //First turn all LEDs off |
|
{ |
|
digitalWrite(correctNumLEDs[i], LOW); |
|
digitalWrite(correctPlaceLEDs[i], LOW); |
|
} |
|
for(int j=0 ; j<=corNum-1 ; j++) //Turn on the number of correct digits in wrong place LEDs |
|
{ |
|
digitalWrite(correctNumLEDs[j], HIGH); |
|
} |
|
for(int k=0 ; k<=corPla-1 ; k++) //Turn on the number of correct digits in the correct place LEDs |
|
{ |
|
Serial.print("Turn correct place led number: "); |
|
Serial.println(correctPlaceLEDs[k]); |
|
|
|
digitalWrite(correctPlaceLEDs[k], HIGH); |
|
} |
|
} |
|
|
|
void startupAni () |
|
{ |
|
display.setTextSize(2); //Set the display text size |
|
display.setCursor(35,10); //Set the display cursor position |
|
display.println(F("Crack")); //Set the display text |
|
display.display(); //Output the display text |
|
delay(500); |
|
display.clearDisplay(); //Clear the display |
|
display.setCursor(45,10); |
|
display.println(F("The")); |
|
display.display(); |
|
delay(500); |
|
display.clearDisplay(); |
|
display.setCursor(40,10); |
|
display.println(F("Code")); |
|
display.display(); |
|
delay(500); |
|
display.clearDisplay(); |
|
} |