Can someome PLEASE help with an issue on an arduino based safe box I have made? Very time sensitive!

I’d do something like

lockServo.attach(5);
delay(20):
lockServo.write(45);

As for the power thing… You could add a bigger cap (1000uF or something) across the power lines. But, 9v batteries are just notoriously bad for… well anything. Don’t know why people keep wanting to use them. Plus the way this is wired up, servo behind the omboard regulator is just bad practice. I’d run past the dollar store and find the cheapest powerbank you can find. Hook that up to the USB port or to the 5v pin and all should be fine. Good luck!

Tim, where would you place it? At the beginning before the serial.begin line that Sebastian suggested?

This can just go anywhere in the setup(), somewhere on the top of it. :wink:

no matter where I place it in this function I receive errors

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
}
b:\Dropbox\Transfers\Safe Box\CrackTheCode\code-edit-02\code-edit-02.ino: In function 'void setup()':
b:\Dropbox\Transfers\Safe Box\CrackTheCode\code-edit-02\code-edit-02.ino:47:20: error: no matching function for call to 'Servo::attach()'
   lockServo.attach();
                    ^
In file included from b:\Dropbox\Transfers\Safe Box\CrackTheCode\code-edit-02\code-edit-02.ino:12:0:
C:\Program Files (x86)\Arduino\libraries\Servo\src/Servo.h:108:11: note: candidate: uint8_t Servo::attach(int)
   uint8_t attach(int pin);           // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
           ^~~~~~
C:\Program Files (x86)\Arduino\libraries\Servo\src/Servo.h:108:11: note:   candidate expects 1 argument, 0 provided
In file included from b:\Dropbox\Transfers\Safe Box\CrackTheCode\code-edit-02\code-edit-02.ino:12:0:
C:\Program Files (x86)\Arduino\libraries\Servo\src/Servo.h:109:11: note: candidate: uint8_t Servo::attach(int, int, int)
   uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes.
           ^~~~~~
C:\Program Files (x86)\Arduino\libraries\Servo\src/Servo.h:109:11: note:   candidate expects 3 arguments, 0 provided
Compilation error: Error: 2 UNKNOWN: exit status 1

Try this:

20 characters

That code compiled and uploaded, thank you for that!!. When I plug in the USB power the servo moves from the locked position about 15 degrees towards the unlocked position and then goes back down to the locked position. Is there a way to prevent it from doing this? If not I might be able to adjust the lock so that it won’t release when it moves up that 15 degrees. I’m not familiar enough with the code to know. I understand some of it as I know some jQuery and the basics of that are similar to this code.

I do need to check what happens when you solve the puzzle, which means I need to solve it myself…lol. Is there a simple sketch that I could use to setup the servo lock so I don’t have to solve it every time for testing?

1 Like

Cool! Can you show us your full code for that?

This is the full sketch now. I heard back from the designer of this and he said regarding the power this:

“A 9V block battery can’t provide much current. The micro servo on the locking mechanism draws a lot of current when it moves. This likely causes the voltage to dip (probably faster than your multimeter can detect) and this resets the Arduino. You need to use a better battery pack or lithium ion cells.”

//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
  lockServo.attach(5);                                //Assign the lock servo to pin 5
  delay(20);
  lockServo.write(45);                                //Locks the safe when turned on

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

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
}

Well this sure is a… special sketch.

There’s a very odd thing where it actually goes into the ‘correct guess’ sequence on startup. I’ve made a few minor changes to get rid of it, you can try this. But honestly, this thing is so convoluted, I might have ended up ruining it. Obviously I don’t have to system to test it on. It’d be helpful if you could post the serial monitor output alongside the sketch, so we can see what it’s actually doing.

//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 = false;              //Variable to check whether the code has been guessed correctly

void setup()
{  
  Serial.begin(9600);                                 //Starts the Serial monitor for debugging
  lockServo.attach(5);                                //Assign the lock servo to pin 5
  delay(20);
  lockServo.write(45);                                //Locks the safe when turned on

  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
 
  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
  generateNewCode();                                  //Calls function to generate a new random code
}

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

      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
}

Yeah 9V batteries suck. If you know that, why did you design it this way? Anyway, a big cap across the powerlines can help smooth this out. Or, if that’s not enough, LiPo cells (= powerbanks) can deliver much more current.

[edit] also, for debugging, the correct code is written to the serial monitor, so you can read it from that. Or, if you will, you can comment out the generateNewCode(); I’ve put on line 72, so the initial code is always 0000

2 Likes

:joy: :rofl: :sweat_smile: that is hilarious! Agreed!

Yes, this seems to be a "very special sketch " LOL.

I don’t know enough about the code to recognize that but I’m glad that you can and very much appreciate your help and time!

So when I plug in the USB cable using the sketch you just wrote the servo moves as soon as the contact with the cable is made. Theoretically I could open the box if I just kept tapping the circuit…LOL. However, once I push the cable all the way in it then moves back to the locked position. If I do not tap the circuit and just put the cable straight in you can see in the video that the servo moves about 15 degrees and then once the code runs it moves back to the locked position where it started. (see the video showing both things I described.)

I’m wondering though if this would happen if I was not using the USB for power and using the battery? I’m going to try and get a 9v lithium battery tomorrow to test that theory and hopefully get it to run off of battery power.

Link to my dropbox with the video:
https://www.dropbox.com/s/vgzpah9wjt3ahti/20211003_003844.mp4?dl=0

The serial monitor doesn’t show anything but the code to unlock the device. There’s no other data shown. Is there another view or something else I need to do to be able to see what it’s outputting?

2 Likes

You should start seeing more things once you start fiddling with the encoder, but initially this seems fine.

The servo twitch happens before the sketch is loaded it seems, in the time between power up and the arduino bootup. So, there’s nothing you can about it in the code. I remember once reading a solution of putting a 10k pullup on the signal line, can you try that?

1 Like

Does that mean placing a 10k between the servo power wire and the power line from the 5v of the arduino?

10k between the servo signal wire and 5v :wink:

1 Like

Perfect! That’s what I was trying to say. :wink: I will try that.

Is there a place in the code that tells the servo how far to turn to “unlock” and “lock”? I was hoping to be able to play with the degrees of rotation. (I’m not 100% sure how I am going to get it to lock and unlock but have some ideas.)

Those are the 45 and 140 in the servo.write() commands!

Great! Thank you! :slight_smile:

2 Likes