Right now I have made the change to the code so that the servo goes to the locked position when it is turned on. (This is code that the guy who created this suggested on the project page.) I am trying to get this to work so that when they receive the box the servo is already in the locked position. This way they have to turn it on and enter the code before they can open it. Otherwise they could just open it without turning it on.
This is the code I currently have:
//Code Breaker
//Michael Klements
//The DIY Life
//15 May 2020
//Encoder interrupt routine adapted from Simon Merrett's example code
#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
Servo lockServo; //Create a servo object for the lock servo
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET -1 // 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)
static int pinA = 2; //Hardware interrupt digital pin 2
static int pinB = 3; //Hardware interrupt digital pin 3
volatile byte aFlag = 0; //Rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; //Rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //Current value of encoder position, digit being input form 0 to 9
volatile byte prevEncoderPos = 0; //To track whether the encoder has been turned and the display needs to update
volatile byte reading = 0; //Stores direct value from interrupt pin
const byte buttonPin = 4; //Pin number for encoder push button
byte oldButtonState = HIGH; //First button state is open because of pull-up resistor
const unsigned long debounceTime = 10; //Debounce delay time
unsigned long buttonPressTime; //Time button has been pressed for debounce
byte correctNumLEDs[4] = {9,12,7,11}; //Pin numbers for correct number LEDs (Indicate a correct digit)
byte correctPlaceLEDs[4] = {6,10,8,13}; //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
void setup()
{
Serial.begin(9600); //Starts the Serial monitor for debugging
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) //Connect to the OLED display
{
Serial.println(F("SSD1306 allocation failed")); //If connection fails
for(;;); //Don't proceed, loop forever
}
display.clearDisplay(); //Clear display
lockServo.attach(5); //Assign the lock servo to pin 5
for(int i=0 ; i<=3 ; i++) //Define pin modes for the LEDs
{
pinMode(correctNumLEDs[i], OUTPUT);
pinMode(correctPlaceLEDs[i], OUTPUT);
}
pinMode(pinA, INPUT_PULLUP); //Set pinA as an input, pulled HIGH to the logic voltage
pinMode(pinB, INPUT_PULLUP); //Set pinB as an input, pulled HIGH to the logic voltage
attachInterrupt(0,PinA,RISING); //Set an interrupt on PinA
attachInterrupt(1,PinB,RISING); //Set an interrupt on PinB
pinMode (buttonPin, INPUT_PULLUP); //Set the encoder button as an input, pulled HIGH to the logic voltage
randomSeed(analogRead(0)); //Randomly choose a starting point for the random function, otherwise code pattern is predictable
display.setTextColor(SSD1306_WHITE); //Set the text colour to blue
startupAni(); //Display the startup animation
lockServo.write(45); //Locks the safe when turned on
}
void loop()
{
if(correctGuess) //Code between games to reset if the guess is correct, initially true to open safe and then generate new code
{
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
{
lockServo.write(140); //Unlock the safe
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("Start")); //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
boolean pressed = false; //Keeps track of button press
while(!lock) //While button is not pressed, wait for it to be pressed
{
byte buttonState = digitalRead (buttonPin);
if (buttonState != oldButtonState)
{
if (millis () - buttonPressTime >= debounceTime) //Debounce button
{
buttonPressTime = millis (); //Time when button is pressed
oldButtonState = buttonState; //Remember button state
if (buttonState == LOW)
{
pressed = true; //Records button has been pressed
}
else
{
if (pressed == true) //Makes sure that button is pressed and then released before continuing in the code
{
lockServo.write(45); //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
encoderPos = 0; //Reset the encoder position
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: ");
for (int i=0 ; i<= 3 ; i++) //Loops through the four digits and assigns a random number to each
{
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
boolean pressed = false;
encoderPos = 0; //Encoder starts from 0 for each digit
while(!confirmed) //While the user has not confirmed the digit input
{
byte buttonState = digitalRead (buttonPin);
if (buttonState != oldButtonState)
{
if (millis () - buttonPressTime >= debounceTime) //Debounce button
{
buttonPressTime = millis (); //Time when button was pushed
oldButtonState = buttonState; //Remember button state for next time
if (buttonState == LOW)
{
codeGuess[i] = encoderPos; //If the button is pressed, accept the current digit into the guessed code
pressed = true;
}
else
{
if (pressed == true) //Confirm the input once the button is released again
{
updateDisplayCode(); //Update the code being displayed
confirmed = true;
}
}
}
}
if(encoderPos!=prevEncoderPos) //Update the displayed code if the encoder position has changed
{
updateDisplayCode();
prevEncoderPos=encoderPos;
}
}
}
}
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
}
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
{
digitalWrite(correctPlaceLEDs[k], HIGH);
}
}
void startupAni ()
{
display.setTextSize(2); //Set the display text size
display.setCursor(30,10); //Set the display cursor position
display.println(F("Charlie")); //Set the display text
display.display(); //Output the display text
delay(700);
display.clearDisplay(); //Clear the display
display.setCursor(45,10); //Set the display cursor position
display.println(F("and")); //Set the display text
display.display(); //Output the display text
delay(700);
display.clearDisplay(); //Clear the display
display.setCursor(35,10); //Set the display cursor position
display.println(F("Mike")); //Set the display text
display.display(); //Output the display text
delay(700);
display.clearDisplay(); //Clear the display
display.setCursor(35,10); //Set the display cursor position
display.println(F("Crack")); //Set the display text
display.display(); //Output the display text
delay(700);
display.clearDisplay(); //Clear the display
display.setCursor(45,10);
display.println(F("the"));
display.display();
delay(700);
display.clearDisplay();
display.setCursor(40,10);
display.println(F("Code"));
display.display();
delay(700);
display.clearDisplay();
}
void PinA() //Rotary encoder interrupt service routine for one encoder pin
{
cli(); //Stop interrupts happening before we read pin values
reading = PIND & 0xC; //Read all eight pin values then strip away all but pinA and pinB's values
if(reading == B00001100 && aFlag) //Check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
{
if(encoderPos>0)
encoderPos --; //Decrement the encoder's position count
else
encoderPos = 9; //Go back to 9 after 0
bFlag = 0; //Reset flags for the next turn
aFlag = 0; //Reset flags for the next turn
}
else if (reading == B00000100) //Signal that we're expecting pinB to signal the transition to detent from free rotation
bFlag = 1;
sei(); //Restart interrupts
}
void PinB() //Rotary encoder interrupt service routine for the other encoder pin
{
cli(); //Stop interrupts happening before we read pin values
reading = PIND & 0xC; //Read all eight pin values then strip away all but pinA and pinB's values
if (reading == B00001100 && bFlag) //Check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
{
if(encoderPos<9)
encoderPos ++; //Increment the encoder's position count
else
encoderPos = 0; //Go back to 0 after 9
bFlag = 0; //Reset flags for the next turn
aFlag = 0; //Reset flags for the next turn
}
else if (reading == B00001000) //Signal that we're expecting pinA to signal the transition to detent from free rotation
aFlag = 1;
sei(); //Restart interrupts
}