Electronic Safe
This project implements an electronic safe, powered by an Arduino Uno.
The safe has three main components: An 16x02 LCD Monitor and a Membrane Keypad for the user interface, and a Servo motor that powers the lock mechanism.
The state of the safe (locked/unlocked), as well as the secret code are stored in the Arduino's EEPROM, so the code isn't wiped even when the power goes off.
Code Structure
The code is divided into three modules:
- electronic-safe.ino - Main program code, including the user interface
- icons.cpp - Provides the locked/unlocked icon for the LCD screen
- SafeState.cpp - Manages the state of the safe and the secret code, and stores them in the EEPROM memory.
Hardware
Item | Quantity | Notes |
---|---|---|
Arduino Uno R3 | 1 | |
Membrane KeyPad | 1 | |
LCD1602 | 1 | |
Servo motor | 1 | You can use a solenoid instead |
Diagram
Pin Connections
Arduino Pin | Device | Device Pin |
---|---|---|
12 | LCD | RS |
11 | LCD | Enable |
10 | LCD | D4 |
9 | LCD | D5 |
8 | LCD | D6 |
7 | LCD | D7 |
6 | Servo | Signal |
5 | Keypad | R1 |
4 | Keypad | R2 |
3 | Keypad | R3 |
2 | Keypad | R4 |
A3 | Keypad | C1 |
A2 | Keypad | C2 |
A1 | Keypad | C3 |
A0 | Keypad | C4 |
Source code
electronic-safe.ino
/**
Arduino Electronic Safe
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <Servo.h>
#include "SafeState.h"
#include "icons.h"
/* Locking mechanism definitions */
#define SERVO_PIN 6
#define SERVO_LOCK_POS 20
#define SERVO_UNLOCK_POS 90
Servo lockServo;
/* Display */
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
/* Keypad setup */
const byte KEYPAD_ROWS = 4;
const byte KEYPAD_COLS = 4;
byte rowPins[KEYPAD_ROWS] = {5, 4, 3, 2};
byte colPins[KEYPAD_COLS] = {A3, A2, A1, A0};
char keys[KEYPAD_ROWS][KEYPAD_COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, KEYPAD_ROWS, KEYPAD_COLS);
/* SafeState stores the secret code in EEPROM */
SafeState safeState;
void lock() {
lockServo.write(SERVO_LOCK_POS);
safeState.lock();
}
void unlock() {
lockServo.write(SERVO_UNLOCK_POS);
}
void showStartupMessage() {
lcd.setCursor(4, 0);
lcd.print("Welcome!");
delay(1000);
lcd.setCursor(0, 2);
String message = "ArduinoSafe v1.0";
for (byte i = 0; i < message.length(); i++) {
lcd.print(message[i]);
delay(100);
}
delay(500);
}
String inputSecretCode() {
lcd.setCursor(5, 1);
lcd.print("[____]");
lcd.setCursor(6, 1);
String result = "";
while (result.length() < 4) {
char key = keypad.getKey();
if (key >= '0' && key <= '9') {
lcd.print('*');
result += key;
}
}
return result;
}
void showWaitScreen(int delayMillis) {
lcd.setCursor(2, 1);
lcd.print("[..........]");
lcd.setCursor(3, 1);
for (byte i = 0; i < 10; i++) {
delay(delayMillis);
lcd.print("=");
}
}
bool setNewCode() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter new code:");
String newCode = inputSecretCode();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Confirm new code");
String confirmCode = inputSecretCode();
if (newCode.equals(confirmCode)) {
safeState.setCode(newCode);
return true;
} else {
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("Code mismatch");
lcd.setCursor(0, 1);
lcd.print("Safe not locked!");
delay(2000);
return false;
}
}
void showUnlockMessage() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.write(ICON_UNLOCKED_CHAR);
lcd.setCursor(4, 0);
lcd.print("Unlocked!");
lcd.setCursor(15, 0);
lcd.write(ICON_UNLOCKED_CHAR);
delay(1000);
}
void safeUnlockedLogic() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.write(ICON_UNLOCKED_CHAR);
lcd.setCursor(2, 0);
lcd.print(" # to lock");
lcd.setCursor(15, 0);
lcd.write(ICON_UNLOCKED_CHAR);
bool newCodeNeeded = true;
if (safeState.hasCode()) {
lcd.setCursor(0, 1);
lcd.print(" A = new code");
newCodeNeeded = false;
}
auto key = keypad.getKey();
while (key != 'A' && key != '#') {
key = keypad.getKey();
}
bool readyToLock = true;
if (key == 'A' || newCodeNeeded) {
readyToLock = setNewCode();
}
if (readyToLock) {
lcd.clear();
lcd.setCursor(5, 0);
lcd.write(ICON_UNLOCKED_CHAR);
lcd.print(" ");
lcd.write(ICON_RIGHT_ARROW);
lcd.print(" ");
lcd.write(ICON_LOCKED_CHAR);
safeState.lock();
lock();
showWaitScreen(100);
}
}
void safeLockedLogic() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.write(ICON_LOCKED_CHAR);
lcd.print(" Safe Locked! ");
lcd.write(ICON_LOCKED_CHAR);
String userCode = inputSecretCode();
bool unlockedSuccessfully = safeState.unlock(userCode);
showWaitScreen(200);
if (unlockedSuccessfully) {
showUnlockMessage();
unlock();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access Denied!");
showWaitScreen(1000);
}
}
void setup() {
lcd.begin(16, 2);
init_icons(lcd);
lockServo.attach(SERVO_PIN);
/* Make sure the physical lock is sync with the EEPROM state */
Serial.begin(115200);
if (safeState.locked()) {
lock();
} else {
unlock();
}
showStartupMessage();
}
void loop() {
if (safeState.locked()) {
safeLockedLogic();
} else {
safeUnlockedLogic();
}
}
SafeState.cpp
/**
Arduino Electronic Safe
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#include <Arduino.h>
#include <EEPROM.h>
#include "SafeState.h"
/* Safe state */
#define EEPROM_ADDR_LOCKED 0
#define EEPROM_ADDR_CODE_LEN 1
#define EEPROM_ADDR_CODE 2
#define EEPROM_EMPTY 0xff
#define SAFE_STATE_OPEN (char)0
#define SAFE_STATE_LOCKED (char)1
SafeState::SafeState() {
this->_locked = EEPROM.read(EEPROM_ADDR_LOCKED) == SAFE_STATE_LOCKED;
}
void SafeState::lock() {
this->setLock(true);
}
bool SafeState::locked() {
return this->_locked;
}
bool SafeState::hasCode() {
auto codeLength = EEPROM.read(EEPROM_ADDR_CODE_LEN);
return codeLength != EEPROM_EMPTY;
}
void SafeState::setCode(String newCode) {
EEPROM.write(EEPROM_ADDR_CODE_LEN, newCode.length());
for (byte i = 0; i < newCode.length(); i++) {
EEPROM.write(EEPROM_ADDR_CODE + i, newCode[i]);
}
}
bool SafeState::unlock(String code) {
auto codeLength = EEPROM.read(EEPROM_ADDR_CODE_LEN);
if (codeLength == EEPROM_EMPTY) {
// There was no code, so unlock always succeeds
this->setLock(false);
return true;
}
if (code.length() != codeLength) {
return false;
}
for (byte i = 0; i < code.length(); i++) {
auto digit = EEPROM.read(EEPROM_ADDR_CODE + i);
if (digit != code[i]) {
return false;
}
}
this->setLock(false);
return true;
}
void SafeState::setLock(bool locked) {
this->_locked = locked;
EEPROM.write(EEPROM_ADDR_LOCKED, locked ? SAFE_STATE_LOCKED : SAFE_STATE_OPEN);
}
SafeState.h
/**
Arduino Electronic Safe
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#ifndef SAFESTATE_H
#define SAFESTATE_H
class SafeState {
public:
SafeState();
void lock();
bool unlock(String code);
bool locked();
bool hasCode();
void setCode(String newCode);
private:
void setLock(bool locked);
bool _locked;
};
#endif /* SAFESTATE_H */
icons.cpp
/**
Arduino Electronic Safe
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#include <Arduino.h>
#include "icons.h"
const byte iconLocked[8] PROGMEM = {
0b01110,
0b10001,
0b10001,
0b11111,
0b11011,
0b11011,
0b11111,
};
const byte iconUnlocked[8] PROGMEM = {
0b01110,
0b10000,
0b10000,
0b11111,
0b11011,
0b11011,
0b11111,
};
void init_icons(LiquidCrystal &lcd) {
byte icon[8];
memcpy_P(icon, iconLocked, sizeof(icon));
lcd.createChar(ICON_LOCKED_CHAR, icon);
memcpy_P(icon, iconUnlocked, sizeof(icon));
lcd.createChar(ICON_UNLOCKED_CHAR, icon);
}
icons.h
/**
Arduino Electronic Safe
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#ifndef ICONS_H
#define ICONS_H
#include <LiquidCrystal.h>
// Our custom icon numbers
#define ICON_LOCKED_CHAR (byte)0
#define ICON_UNLOCKED_CHAR (byte)1
// This is a standard icon on the LCD1602 character set
#define ICON_RIGHT_ARROW (byte)126
void init_icons(LiquidCrystal &lcd);
#endif /* ICONS_H */