#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <LiquidCrystal.h>
#include <Keypad.h>
#include <ESP32Servo.h>
#include <Preferences.h>
#include <Adafruit_NeoPixel.h>
#include <mbedtls/md.h>
// --- CONFIGURATION RESEAU & DISCORD ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* discord_webhook = "https://ptb.discord.com/api/webhooks/1483457251275702436/_NCw97kLkH_pH7fDGi2cIrWpaJrSkWeEftSCxmqVpfzQ3wulhFrrWMVkmZgzNrDfG2-r";
// --- CONFIGURATION MATERIELLE ---
#define PIN_NEOPIXEL 2
#define NUM_PIXELS 80
#define PIN_SERVO 13
// --- PROTOTYPES ---
void checkLockStatus();
void handleLockedState();
void handleUnlockedState(char key);
void updateDisplay(String line1, String line2 = "");
void setRingColor(uint32_t color);
bool setNewCode();
String inputSecretCode(String label);
String hashSHA256(String payload);
void sendDiscordMessage(String message);
// --- OBJETS ---
LiquidCrystal lcd(19, 18, 5, 17, 16, 4);
Servo myServo;
Adafruit_NeoPixel ring(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
Preferences prefs;
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {{'1','2','3','A'},{'4','5','6','B'},{'7','8','9','C'},{'*','0','#','D'}};
byte rowPins[ROWS] = {12, 14, 27, 26}, colPins[COLS] = {25, 33, 32, 21};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// --- VARIABLES ---
String secretCodeHash = "";
bool isSafeLocked = false;
bool isSystemBlocked = false;
unsigned long blockUntil = 0;
int attempts = 0;
void setup() {
Serial.begin(115200);
lcd.begin(16, 2);
ring.begin();
ring.show();
myServo.attach(PIN_SERVO);
WiFi.begin(ssid, password);
prefs.begin("safe", false);
secretCodeHash = prefs.getString("codeHash", "");
isSafeLocked = false;
myServo.write(90);
setRingColor(ring.Color(0, 255, 0));
if (secretCodeHash == "") {
updateDisplay("Initialisation", "A: Creer code");
} else {
updateDisplay("Systeme Pret", "A: Modif | #: Lock");
}
}
void loop() {
checkLockStatus();
if (!isSystemBlocked) {
if (isSafeLocked) {
handleLockedState();
} else {
char key = keypad.getKey();
if (key) handleUnlockedState(key);
}
}
}
// --- INTERFACE : SAISIE CODE AVEC LABEL ---
String inputSecretCode(String label) {
String result = "";
updateDisplay(label, "[____]");
while (result.length() < 4) {
char key = keypad.getKey();
if (key >= '0' && key <= '9') {
result += key;
String mask = "";
for (int i = 0; i < 4; i++) mask += (i < result.length()) ? "*" : "_";
lcd.setCursor(0, 1);
lcd.print("[" + mask + "]");
}
if (key == '*') {
result = "";
lcd.setCursor(0, 1);
lcd.print("[____]");
}
yield();
}
delay(200);
return result;
}
// --- LOGIQUE DE CREATION / MODIFICATION DU CODE ---
bool setNewCode() {
String code1 = inputSecretCode("Nouveau code:");
String code2 = inputSecretCode("Confirmer:");
if (code1 == code2) {
secretCodeHash = hashSHA256(code1);
prefs.putString("codeHash", secretCodeHash);
updateDisplay("Code Enregistre!", "Securite OK");
sendDiscordMessage("🔐 **Nouveau code configuré avec succès.**");
delay(2000);
return true;
} else {
updateDisplay("Erreur:", "Codes differents");
setRingColor(ring.Color(255, 0, 0));
delay(2000);
setRingColor(ring.Color(0, 255, 0));
return false;
}
}
void handleUnlockedState(char key) {
if (key == '#') {
if (secretCodeHash == "") {
updateDisplay("Erreur", "Creez code (A)");
delay(2000);
updateDisplay("Systeme Pret", "A: Modif | #: Lock");
} else {
isSafeLocked = true;
myServo.write(0);
setRingColor(ring.Color(0, 0, 0));
updateDisplay("Coffre Ferme", "[____]");
sendDiscordMessage("🔒 **Coffre verrouillé.**");
}
}
else if (key == 'A') {
setNewCode();
updateDisplay("Systeme Pret", "A: Modif | #: Lock");
}
}
void handleLockedState() {
String userCode = inputSecretCode("Entrez code:");
if (hashSHA256(userCode) == secretCodeHash) {
isSafeLocked = false;
attempts = 0;
myServo.write(90);
setRingColor(ring.Color(0, 255, 0));
updateDisplay("Acces Autorise", "A: Modif | #: Lock");
sendDiscordMessage("✅ **Coffre déverrouillé.**");
} else {
attempts++;
setRingColor(ring.Color(255, 0, 0));
updateDisplay("Code Errone!", "Essai " + String(attempts) + "/3");
delay(1500);
setRingColor(ring.Color(0, 0, 0));
if (attempts >= 3) {
isSystemBlocked = true;
blockUntil = millis() + 30000;
sendDiscordMessage("❌ **ALERTE** : Système bloqué (3 erreurs).");
} else {
updateDisplay("Coffre Ferme", "[____]");
}
}
}
String hashSHA256(String payload) {
byte shaResult[32];
mbedtls_md_context_t ctx;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, (const unsigned char *)payload.c_str(), payload.length());
mbedtls_md_finish(&ctx, shaResult);
mbedtls_md_free(&ctx);
String hashStr = "";
for (int i = 0; i < 32; i++) {
char buf[3];
sprintf(buf, "%02x", shaResult[i]);
hashStr += buf;
}
return hashStr;
}
void checkLockStatus() {
if (isSystemBlocked) {
if (millis() < blockUntil) {
lcd.setCursor(0, 0);
lcd.print("SYSTEME BLOQUE ");
lcd.setCursor(0, 1);
lcd.print("Attendre: " + String((blockUntil - millis()) / 1000) + "s ");
setRingColor(ring.Color(255, 0, 0));
} else {
isSystemBlocked = false;
attempts = 0;
setRingColor(ring.Color(0, 0, 0));
updateDisplay("Coffre Ferme", "[____]");
}
}
}
void updateDisplay(String line1, String line2) {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(line1);
lcd.setCursor(0, 1); lcd.print(line2);
}
void setRingColor(uint32_t color) {
for(int i=0; i<NUM_PIXELS; i++) ring.setPixelColor(i, color);
ring.show();
}
void sendDiscordMessage(String message) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(discord_webhook);
http.addHeader("Content-Type", "application/json");
String jsonPayload = "{\"content\": \"" + message + "\"}";
http.POST(jsonPayload);
http.end();
}
}