/*
* IR Remote Password Lock — 3 Device Controller
* ================================================
* - 3 devices (relay/LED outputs) toggled ON/OFF
* - All devices protected behind password lock
* - Default password: 1234
* - User can change password (saved to EEPROM)
* - Device states saved to EEPROM (persist across reboot)
* - Red LED = Locked | Green LED = Unlocked
*
* BUTTONS (when LOCKED):
* 0-9 : Enter password digits
* PLAY : Submit password
* C : Clear input
*
* BUTTONS (when UNLOCKED):
* 1 : Toggle Device 1
* 2 : Toggle Device 2
* 3 : Toggle Device 3
* MENU : Change password
* POWER : Lock system
* C : All devices OFF
* PLUS : All devices ON
*
* CHANGE PASSWORD MODE:
* 0-9 : Enter digits
* PLAY : Confirm
* BACK/C : Cancel
*/
#include <IRremote.hpp>
#include <LiquidCrystal.h>
#include <EEPROM.h>
// ───── Pin Definitions ─────
#define IR_RECEIVE_PIN 2
#define GREEN_LED_PIN 10 // Unlocked indicator
#define RED_LED_PIN 11 // Locked indicator
#define DEVICE1_PIN A0 // Device 1 output (relay/LED)
#define DEVICE2_PIN A1 // Device 2 output (relay/LED)
#define DEVICE3_PIN A2 // Device 3 output (relay/LED)
// ───── EEPROM Addresses ─────
#define EEPROM_FLAG_ADDR 0 // Magic flag
#define EEPROM_LEN_ADDR 1 // Password length
#define EEPROM_PASS_ADDR 2 // Password digits (up to 8)
#define EEPROM_DEV_STATE 12 // Device states (3 bytes)
#define EEPROM_FLAG_VALUE 42 // Magic number for first-boot detection
// ───── Constants ─────
#define MAX_PASS_LEN 8
#define DEFAULT_PASS "1234"
#define NUM_DEVICES 3
// ───── System States ─────
enum State {
STATE_LOCKED,
STATE_UNLOCKED,
STATE_CHANGE_PASS_OLD,
STATE_CHANGE_PASS_NEW,
STATE_CHANGE_PASS_CONFIRM
};
// ───── Global Variables ─────
LiquidCrystal lcd(9, 8, 7, 6, 5, 4);
State currentState = STATE_LOCKED;
char storedPassword[MAX_PASS_LEN + 1];
char inputBuffer[MAX_PASS_LEN + 1];
char newPassBuffer[MAX_PASS_LEN + 1];
int inputIndex = 0;
int failedAttempts = 0;
unsigned long lockoutTime = 0;
bool lockedOut = false;
// Device state tracking
bool deviceState[NUM_DEVICES] = {false, false, false};
const int devicePin[NUM_DEVICES] = {DEVICE1_PIN, DEVICE2_PIN, DEVICE3_PIN};
// =====================================================
// SETUP
// =====================================================
void setup() {
Serial.begin(9600);
// LED pins
pinMode(GREEN_LED_PIN, OUTPUT);
pinMode(RED_LED_PIN, OUTPUT);
// Device output pins
for (int i = 0; i < NUM_DEVICES; i++) {
pinMode(devicePin[i], OUTPUT);
digitalWrite(devicePin[i], LOW);
}
// IR Receiver
IrReceiver.begin(IR_RECEIVE_PIN);
// LCD
lcd.begin(16, 2);
// Load password & device states from EEPROM
loadPasswordFromEEPROM();
loadDeviceStatesFromEEPROM();
// Apply saved device states to output pins
applyDeviceOutputs();
// Start locked
currentState = STATE_LOCKED;
setLEDs(false);
displayLocked();
Serial.println("IR Password Lock - 3 Device Controller Ready");
}
// =====================================================
// MAIN LOOP
// =====================================================
void loop() {
// Handle lockout (30s after 3 wrong attempts)
if (lockedOut) {
if (millis() - lockoutTime >= 30000) {
lockedOut = false;
failedAttempts = 0;
displayLocked();
} else {
digitalWrite(RED_LED_PIN, (millis() / 250) % 2); // Blink red
return;
}
}
// Check IR signal
if (IrReceiver.decode()) {
int command = IrReceiver.decodedIRData.command;
switch (currentState) {
case STATE_LOCKED: handleLocked(command); break;
case STATE_UNLOCKED: handleUnlocked(command); break;
case STATE_CHANGE_PASS_OLD: handleChangePassOld(command); break;
case STATE_CHANGE_PASS_NEW: handleChangePassNew(command); break;
case STATE_CHANGE_PASS_CONFIRM: handleChangePassConfirm(command); break;
}
IrReceiver.resume();
}
}
// =====================================================
// EEPROM — PASSWORD
// =====================================================
void loadPasswordFromEEPROM() {
if (EEPROM.read(EEPROM_FLAG_ADDR) == EEPROM_FLAG_VALUE) {
int len = EEPROM.read(EEPROM_LEN_ADDR);
if (len > 0 && len <= MAX_PASS_LEN) {
for (int i = 0; i < len; i++) {
storedPassword[i] = EEPROM.read(EEPROM_PASS_ADDR + i);
}
storedPassword[len] = '\0';
Serial.println("Password loaded from EEPROM");
return;
}
}
// First boot — set default
strcpy(storedPassword, DEFAULT_PASS);
savePasswordToEEPROM(storedPassword);
}
void savePasswordToEEPROM(const char* password) {
int len = strlen(password);
EEPROM.update(EEPROM_FLAG_ADDR, EEPROM_FLAG_VALUE);
EEPROM.update(EEPROM_LEN_ADDR, len);
for (int i = 0; i < len; i++) {
EEPROM.update(EEPROM_PASS_ADDR + i, password[i]);
}
}
// =====================================================
// EEPROM — DEVICE STATES
// =====================================================
void loadDeviceStatesFromEEPROM() {
if (EEPROM.read(EEPROM_FLAG_ADDR) == EEPROM_FLAG_VALUE) {
for (int i = 0; i < NUM_DEVICES; i++) {
deviceState[i] = EEPROM.read(EEPROM_DEV_STATE + i) == 1;
}
}
}
void saveDeviceStatesToEEPROM() {
for (int i = 0; i < NUM_DEVICES; i++) {
EEPROM.update(EEPROM_DEV_STATE + i, deviceState[i] ? 1 : 0);
}
}
// =====================================================
// DEVICE CONTROL
// =====================================================
void applyDeviceOutputs() {
for (int i = 0; i < NUM_DEVICES; i++) {
digitalWrite(devicePin[i], deviceState[i] ? HIGH : LOW);
}
}
void toggleDevice(int index) {
if (index < 0 || index >= NUM_DEVICES) return;
deviceState[index] = !deviceState[index];
digitalWrite(devicePin[index], deviceState[index] ? HIGH : LOW);
saveDeviceStatesToEEPROM();
// Brief feedback on LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Device ");
lcd.print(index + 1);
lcd.print(deviceState[index] ? " : ON " : " : OFF");
lcd.setCursor(0, 1);
displayDeviceBar();
delay(800);
displayUnlocked();
}
void setAllDevices(bool state) {
for (int i = 0; i < NUM_DEVICES; i++) {
deviceState[i] = state;
}
applyDeviceOutputs();
saveDeviceStatesToEEPROM();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(state ? "All Devices ON" : "All Devices OFF");
delay(1000);
displayUnlocked();
}
// =====================================================
// LED CONTROL
// =====================================================
void setLEDs(bool unlocked) {
digitalWrite(GREEN_LED_PIN, unlocked ? HIGH : LOW);
digitalWrite(RED_LED_PIN, unlocked ? LOW : HIGH);
}
// =====================================================
// LCD DISPLAY
// =====================================================
void displayLocked() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("=== LOCKED ===");
lcd.setCursor(0, 1);
lcd.print("Enter Password:");
clearInput();
}
void displayUnlocked() {
lcd.clear();
lcd.setCursor(0, 0);
// Show device status: D1:ON D2:OFF D3:ON
displayDeviceBar();
lcd.setCursor(0, 1);
lcd.print("1-3:Tog MN:Pw PW:Lk");
}
void displayDeviceBar() {
// Shows: D1:ON D2:OFF D3:ON
for (int i = 0; i < NUM_DEVICES; i++) {
lcd.print("D");
lcd.print(i + 1);
lcd.print(":");
lcd.print(deviceState[i] ? "ON " : "OFF");
if (i < NUM_DEVICES - 1) lcd.print(" ");
}
}
void displayPasswordInput() {
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
for (int i = 0; i < inputIndex; i++) lcd.print('*');
if (inputIndex < MAX_PASS_LEN) lcd.print('_');
}
void displayChangePrompt(const char* line1) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(line1);
clearInput();
displayPasswordInput();
}
// =====================================================
// INPUT HELPERS
// =====================================================
void clearInput() {
memset(inputBuffer, 0, sizeof(inputBuffer));
inputIndex = 0;
}
void addDigit(char digit) {
if (inputIndex < MAX_PASS_LEN) {
inputBuffer[inputIndex++] = digit;
inputBuffer[inputIndex] = '\0';
displayPasswordInput();
}
}
// =====================================================
// STATE: LOCKED
// =====================================================
void handleLocked(int command) {
int digit = getDigit(command);
if (digit >= 0) {
addDigit('0' + digit);
}
else if (command == 168) { // PLAY = Submit
if (inputIndex == 0) return;
if (strcmp(inputBuffer, storedPassword) == 0) {
// ✔ Correct
currentState = STATE_UNLOCKED;
failedAttempts = 0;
setLEDs(true);
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("ACCESS");
lcd.setCursor(2, 1);
lcd.print("GRANTED!");
delay(1500);
displayUnlocked();
} else {
// ✘ Wrong
failedAttempts++;
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("WRONG!");
lcd.setCursor(0, 1);
lcd.print("Attempts: ");
lcd.print(failedAttempts);
delay(1500);
if (failedAttempts >= 3) {
lockedOut = true;
lockoutTime = millis();
lcd.clear();
lcd.print("!! LOCKOUT !!");
lcd.setCursor(0, 1);
lcd.print("Wait 30 seconds");
for (int i = 0; i < 10; i++) {
digitalWrite(RED_LED_PIN, HIGH); delay(100);
digitalWrite(RED_LED_PIN, LOW); delay(100);
}
digitalWrite(RED_LED_PIN, HIGH);
} else {
displayLocked();
}
}
}
else if (command == 176) { // C = Clear
clearInput();
displayLocked();
}
}
// =====================================================
// STATE: UNLOCKED — Device Control
// =====================================================
void handleUnlocked(int command) {
int digit = getDigit(command);
// Toggle devices with buttons 1, 2, 3
if (digit >= 1 && digit <= 3) {
toggleDevice(digit - 1); // 0-indexed
}
else if (command == 162) { // POWER = Lock
currentState = STATE_LOCKED;
setLEDs(false);
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("LOCKING...");
delay(1000);
displayLocked();
}
else if (command == 226) { // MENU = Change password
currentState = STATE_CHANGE_PASS_OLD;
displayChangePrompt("Old Password:");
}
else if (command == 176) { // C = All OFF
setAllDevices(false);
}
else if (command == 2) { // PLUS = All ON
setAllDevices(true);
}
}
// =====================================================
// STATE: CHANGE PASSWORD — Old Password
// =====================================================
void handleChangePassOld(int command) {
int digit = getDigit(command);
if (digit >= 0) {
addDigit('0' + digit);
}
else if (command == 168) { // PLAY
if (strcmp(inputBuffer, storedPassword) == 0) {
currentState = STATE_CHANGE_PASS_NEW;
displayChangePrompt("New Password:");
} else {
lcd.clear();
lcd.print("Wrong old pass!");
delay(1500);
currentState = STATE_UNLOCKED;
displayUnlocked();
}
}
else if (command == 194 || command == 176) { // BACK or C
currentState = STATE_UNLOCKED;
displayUnlocked();
}
}
// =====================================================
// STATE: CHANGE PASSWORD — New Password
// =====================================================
void handleChangePassNew(int command) {
int digit = getDigit(command);
if (digit >= 0) {
addDigit('0' + digit);
}
else if (command == 168) { // PLAY
if (inputIndex < 1) {
lcd.clear();
lcd.print("Min 1 digit!");
delay(1000);
displayChangePrompt("New Password:");
return;
}
strcpy(newPassBuffer, inputBuffer);
currentState = STATE_CHANGE_PASS_CONFIRM;
displayChangePrompt("Confirm New Pw:");
}
else if (command == 194 || command == 176) {
currentState = STATE_UNLOCKED;
displayUnlocked();
}
}
// =====================================================
// STATE: CHANGE PASSWORD — Confirm
// =====================================================
void handleChangePassConfirm(int command) {
int digit = getDigit(command);
if (digit >= 0) {
addDigit('0' + digit);
}
else if (command == 168) { // PLAY
if (strcmp(inputBuffer, newPassBuffer) == 0) {
// Match — save new password
strcpy(storedPassword, newPassBuffer);
savePasswordToEEPROM(storedPassword);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Password Changed");
lcd.setCursor(0, 1);
lcd.print("Saved to EEPROM!");
// Success blink
for (int i = 0; i < 5; i++) {
digitalWrite(GREEN_LED_PIN, LOW); delay(150);
digitalWrite(GREEN_LED_PIN, HIGH); delay(150);
}
delay(1000);
currentState = STATE_UNLOCKED;
setLEDs(true);
displayUnlocked();
} else {
lcd.clear();
lcd.print("Mismatch! Retry");
delay(1500);
currentState = STATE_CHANGE_PASS_NEW;
displayChangePrompt("New Password:");
}
}
else if (command == 194 || command == 176) {
currentState = STATE_UNLOCKED;
displayUnlocked();
}
}
// =====================================================
// IR COMMAND → DIGIT MAPPING
// =====================================================
int getDigit(int command) {
switch (command) {
case 104: return 0;
case 48: return 1;
case 24: return 2;
case 122: return 3;
case 16: return 4;
case 56: return 5;
case 90: return 6;
case 66: return 7;
case 74: return 8;
case 82: return 9;
default: return -1;
}
}