#include <LiquidCrystal_I2C.h>
#include <TM1637.h>
#include <Adafruit_NeoPixel.h>
// ================== PIN CONFIGURATION ==================
constexpr uint8_t PIN_MASTER_SIGNAL = 2;
constexpr uint8_t PIN_END_SIGNAL = A0;
constexpr uint8_t PIN_NEOPIXEL = 5;
constexpr uint8_t PIN_BUZZER = 6;
constexpr uint8_t PIN_POT_1 = A2;
constexpr uint8_t PIN_POT_2 = A3;
constexpr uint8_t PIN_JOY_1_V = 7;
constexpr uint8_t PIN_JOY_1_H = 8;
constexpr uint8_t PIN_JOY_1_B = 9;
constexpr uint8_t PIN_JOY_2_V = 10;
constexpr uint8_t PIN_JOY_2_H = 11;
constexpr uint8_t PIN_JOY_2_B = 12;
constexpr uint8_t PIN_4DIG_CLK = 3;
constexpr uint8_t PIN_4DIG_DIO = 4;
constexpr uint32_t DEBOUNCE_MS = 500;
constexpr uint32_t JOY_UPDATE_MS = 150; // Rate limiting joystick
// ================== OBJECTS ==================
LiquidCrystal_I2C lcd(0x27, 16, 2);
TM1637 display4d(PIN_4DIG_CLK, PIN_4DIG_DIO);
Adafruit_NeoPixel neo(8, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
// ================== STATE MACHINE ==================
enum class SlaveState : uint8_t {
IDLE,
ACTIVE,
PHASE1,
PHASE2,
END
};
struct Slave {
SlaveState state = SlaveState::IDLE;
uint32_t phaseStart = 0;
} slave;
// ================== PHASE 1 VARIABLES ==================
constexpr int MIN_DISTANCE = 5;
int Sequence[4][2];
bool Ch1 = false, Ch2 = false, Ch3 = false, Ch4 = false;
// ================== PHASE 2 VARIABLES ==================
int Cifre[16];
int Codice[4];
int mappaOrder[4] = {1, 2, 3, 4};
int PosDisplay[5] = {99, 0, 4, 8, 12};
int VAL_M = 0, VAL_C = 0, VAL_D = 0, VAL_U = 0;
int Passo = 1;
bool FInit = false;
uint32_t lastJoyUpdate = 0;
bool lastButtonState = HIGH;
uint32_t buttonDebounceTime = 0;
// ================== HELPER FUNCTIONS ==================
void playTone(int freq, int dur) {
tone(PIN_BUZZER, freq);
delay(dur);
noTone(PIN_BUZZER);
}
int readStablePot(int pin) {
long sum = 0;
for (int i = 0; i < 10; i++) {
sum += analogRead(pin);
delay(2);
}
return sum / 10;
}
void setNeoPixel(uint8_t index, uint32_t color) {
if (index < 8) {
neo.setPixelColor(index, color);
neo.show();
}
}
void setAllNeoPixels(uint32_t color) {
for (int i = 0; i < 8; i++) {
neo.setPixelColor(i, color);
}
neo.show();
}
void setNeoPixelsBrightness(uint8_t brightness) {
neo.setBrightness(brightness);
neo.show();
}
// ================== PHASE 1 LOGIC ==================
void generaCombinazioniFase1() {
bool used[17] = {false};
for (int i = 0; i < 4; i++) {
int p1, p2;
bool valid = false;
while (!valid) {
p1 = random(1, 17);
if (used[p1]) continue;
do {
p2 = random(1, 17);
} while (abs(p1 - p2) < MIN_DISTANCE || used[p2]);
valid = true;
}
Sequence[i][0] = p1;
Sequence[i][1] = p2;
used[p1] = true;
used[p2] = true;
}
// Shuffle resolution order
for (int i = 3; i > 0; i--) {
int j = random(0, i + 1);
int temp0 = Sequence[i][0];
int temp1 = Sequence[i][1];
Sequence[i][0] = Sequence[j][0];
Sequence[i][1] = Sequence[j][1];
Sequence[j][0] = temp0;
Sequence[j][1] = temp1;
}
}
bool checkConditions(int P1, int P2) {
bool* flags[] = {&Ch1, &Ch2, &Ch3, &Ch4};
for (int i = 0; i < 4; i++) {
if (P1 == Sequence[i][0] && P2 == Sequence[i][1] && !(*flags[i])) {
setNeoPixel(i, neo.Color(0, 255, 0)); // Green LED 0-3
playTone(1500, 100);
*flags[i] = true;
}
}
return (Ch1 && Ch2 && Ch3 && Ch4);
}
bool phase1(uint32_t now) {
char S_A[17] = " ";
char S_B[17] = " ";
int P1 = map(readStablePot(PIN_POT_1), 0, 1023, 1, 16);
int P2 = map(readStablePot(PIN_POT_2), 0, 1023, 1, 16);
for (int i = 1; i <= 16; i++) {
S_A[i-1] = (i == P1) ? '|' : ' ';
S_B[i-1] = (i == P2) ? '|' : ' ';
}
lcd.setCursor(0, 0); lcd.print(S_A);
lcd.setCursor(0, 1); lcd.print(S_B);
if (checkConditions(P1, P2)) {
setAllNeoPixels(neo.Color(0, 255, 0)); // All green
playTone(2000, 300);
delay(1000);
return true; // Fase 1 completata
}
return false;
}
// ================== PHASE 2 LOGIC ==================
void GeneraCodici() {
long seed = analogRead(PIN_POT_1) + analogRead(PIN_JOY_2_V) + micros();
randomSeed(seed);
for (int i = 0; i < 16; i++) {
int n;
do { n = random(10); } while (i > 0 && n == Cifre[i-1]);
Cifre[i] = n;
}
for (int i = 0; i < 4; i++) {
bool ok;
do {
ok = true;
Codice[i] = random(10);
for (int j = 0; j < i; j++) if (Codice[i] == Codice[j]) ok = false;
} while (!ok);
}
}
void mescolaMappeJoystick() {
for (int i = 3; i > 0; i--) {
int j = random(0, i + 1);
int temp = mappaOrder[i];
mappaOrder[i] = mappaOrder[j];
mappaOrder[j] = temp;
}
}
bool checkOK(int B, int m, int c, int d, int u) {
int pos = PosDisplay[B];
if (Cifre[pos] == m &&
Cifre[pos+1] == c &&
Cifre[pos+2] == d &&
Cifre[pos+3] == u) {
lcd.setCursor(pos, 1);
lcd.print(m); lcd.print(c); lcd.print(d); lcd.print(u);
return true;
}
return false;
}
bool phase2(uint32_t now) {
if (!FInit) {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(" GENERAZIONE ");
lcd.setCursor(0, 1); lcd.print(" CODICE ACCESSO ");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0); lcd.print("DECODIFICA DATI ");
for (int i = 0; i < 16; i++) {
lcd.setCursor(i, 1); lcd.print("*"); delay(150);
}
lcd.clear();
GeneraCodici();
mescolaMappeJoystick();
playTone(1000, 200);
for (int i = 0; i < 16; i++) {
lcd.setCursor(i, 0); lcd.print(Cifre[i]);
}
lcd.setCursor(0, 1); lcd.print("0000");
// Reset NeoPixel per fase 2 (LED 4-7 rossi per i 4 passi)
for (int i = 4; i < 8; i++) {
neo.setPixelColor(i, neo.Color(255, 0, 0)); // Red
}
neo.show();
lastJoyUpdate = now;
lastButtonState = HIGH;
buttonDebounceTime = 0;
FInit = true;
}
// Rate limiting joystick per evitare increment troppo veloci
if (now - lastJoyUpdate >= JOY_UPDATE_MS) {
lastJoyUpdate = now;
int H1 = analogRead(PIN_JOY_1_H);
int V1 = analogRead(PIN_JOY_1_V);
int H2 = analogRead(PIN_JOY_2_H);
int V2 = analogRead(PIN_JOY_2_V);
int mappaAttuale = mappaOrder[Passo - 1];
switch (mappaAttuale) {
case 1:
if (V1 < 300) VAL_U++; else if (V1 > 700) VAL_C--;
if (H1 > 700) VAL_C++; else if (H1 < 300) VAL_D--;
if (V2 < 300) VAL_M++; else if (V2 > 700) VAL_U--;
if (H2 > 700) VAL_D++; else if (H2 < 300) VAL_M--;
break;
case 2:
if (V1 < 300) VAL_D--; else if (V1 > 700) VAL_C--;
if (H1 > 700) VAL_U++; else if (H1 < 300) VAL_M--;
if (V2 < 300) VAL_D++; else if (V2 > 700) VAL_C++;
if (H2 > 700) VAL_M++; else if (H2 < 300) VAL_U--;
break;
case 3:
if (V1 < 300) VAL_U--; else if (V1 > 700) VAL_D--;
if (H1 > 700) VAL_M++; else if (H1 < 300) VAL_C--;
if (V2 < 300) VAL_D++; else if (V2 > 700) VAL_M--;
if (H2 > 700) VAL_C++; else if (H2 < 300) VAL_U++;
break;
case 4:
if (V1 < 300) VAL_D++; else if (V1 > 700) VAL_U--;
if (H1 > 700) VAL_C++; else if (H1 < 300) VAL_M--;
if (V2 < 300) VAL_U++; else if (V2 > 700) VAL_D--;
if (H2 > 700) VAL_M++; else if (H2 < 300) VAL_C--;
break;
}
VAL_M = constrain(VAL_M, 0, 9);
VAL_C = constrain(VAL_C, 0, 9);
VAL_D = constrain(VAL_D, 0, 9);
VAL_U = constrain(VAL_U, 0, 9);
display4d.display(0, VAL_M);
display4d.display(1, VAL_C);
display4d.display(2, VAL_D);
display4d.display(3, VAL_U);
}
// Gestione pulsante conferma con debounce corretto
bool currentButton = digitalRead(PIN_JOY_1_B);
// Rileva fronte di discesa (HIGH -> LOW)
if (currentButton == LOW && lastButtonState == HIGH) {
buttonDebounceTime = now;
}
// Se il bottone è stato premuto stabilmente per almeno 200ms, processa
if (currentButton == LOW && (now - buttonDebounceTime >= 200)) {
// Processa solo una volta fino al rilascio
if (lastButtonState == HIGH || (now - buttonDebounceTime) == 200) {
if (checkOK(Passo, VAL_M, VAL_C, VAL_D, VAL_U)) {
playTone(2000, 200);
// Accendi LED verde per passo completato (LED 4,5,6,7)
setNeoPixel(3 + Passo, neo.Color(0, 255, 0));
Passo++;
if (Passo == 5) {
// FASE 2 COMPLETATA
setAllNeoPixels(neo.Color(0, 255, 0)); // All green
display4d.display(0, Codice[0]);
display4d.display(1, Codice[1]);
display4d.display(2, Codice[2]);
display4d.display(3, Codice[3]);
lcd.clear();
lcd.setCursor(0, 0); lcd.print(" AUTORIZZATO AL ");
lcd.setCursor(0, 1); lcd.print(" CODICE SBLOCCO ");
delay(3000);
playTone(3000, 500);
return true; // Fase 2 completata
}
} else {
playTone(100, 300);
lcd.setCursor(PosDisplay[Passo], 1); lcd.print("0000");
VAL_M = VAL_C = VAL_D = VAL_U = 0;
display4d.clearDisplay();
}
// Attendi rilascio pulsante
while (digitalRead(PIN_JOY_1_B) == LOW) {
delay(10);
}
}
}
lastButtonState = currentButton;
return false;
}
// ================== INIT FUNCTIONS ==================
void initIdle() {
Ch1 = Ch2 = Ch3 = Ch4 = false;
FInit = false;
Passo = 1;
VAL_M = VAL_C = VAL_D = VAL_U = 0;
lastButtonState = HIGH;
buttonDebounceTime = 0;
lcd.clear();
lcd.noBacklight();
display4d.clearDisplay();
neo.setBrightness(255);
setAllNeoPixels(neo.Color(255, 0, 0)); // Rossi in IDLE
noTone(PIN_BUZZER);
}
void initActive() {
lcd.backlight();
lcd.clear();
display4d.clearDisplay();
neo.setBrightness(255);
setAllNeoPixels(neo.Color(255, 0, 0)); // 8 LED rossi all'inizio
}
void initPhase1() {
randomSeed(micros() + analogRead(PIN_POT_1));
generaCombinazioniFase1();
Ch1 = Ch2 = Ch3 = Ch4 = false;
lcd.clear();
}
void initPhase2() {
FInit = false;
Passo = 1;
VAL_M = VAL_C = VAL_D = VAL_U = 0;
lastButtonState = HIGH;
buttonDebounceTime = 0;
lcd.clear();
display4d.clearDisplay();
}
void initEnd() {
lcd.noBacklight();
// Mostra codice finale
display4d.display(0, Codice[0]);
display4d.display(1, Codice[1]);
display4d.display(2, Codice[2]);
display4d.display(3, Codice[3]);
// 8 LED verdi a bassa luminosità
neo.setBrightness(50);
setAllNeoPixels(neo.Color(0, 255, 0));
}
// ================== SETUP ==================
void setup() {
pinMode(PIN_MASTER_SIGNAL, INPUT);
pinMode(PIN_END_SIGNAL, OUTPUT);
pinMode(PIN_BUZZER, OUTPUT);
pinMode(PIN_POT_1, INPUT);
pinMode(PIN_POT_2, INPUT);
pinMode(PIN_JOY_1_V, INPUT);
pinMode(PIN_JOY_1_H, INPUT);
pinMode(PIN_JOY_1_B, INPUT_PULLUP);
pinMode(PIN_JOY_2_V, INPUT);
pinMode(PIN_JOY_2_H, INPUT);
pinMode(PIN_JOY_2_B, INPUT_PULLUP);
digitalWrite(PIN_END_SIGNAL, LOW);
lcd.init();
lcd.noBacklight();
display4d.init();
display4d.set(BRIGHT_TYPICAL);
neo.begin();
initIdle();
}
// ================== MAIN LOOP ==================
void loop() {
uint32_t now = millis();
static bool prevIdle = false;
static uint32_t edgeTimeIdle = 0;
static bool prevStop = true;
static uint32_t edgeTimeStop = 0;
bool sig = digitalRead(PIN_MASTER_SIGNAL);
// IDLE
if (slave.state == SlaveState::IDLE) {
if (sig != prevIdle) {
prevIdle = sig;
edgeTimeIdle = now;
}
if (sig && (now - edgeTimeIdle >= DEBOUNCE_MS)) {
slave.state = SlaveState::ACTIVE;
slave.phaseStart = now;
}
}
// STOP
else if (slave.state != SlaveState::END) {
if (sig != prevStop) {
prevStop = sig;
if (!sig) {
edgeTimeStop = now;
}
}
if (!sig && (now - edgeTimeStop >= DEBOUNCE_MS)) {
slave.state = SlaveState::END;
initIdle();
}
}
// MACCHINA A STATI
switch (slave.state) {
case SlaveState::IDLE:
// In attesa: LED rossi già accesi
break;
case SlaveState::ACTIVE:
initActive();
initPhase1();
slave.state = SlaveState::PHASE1;
slave.phaseStart = now;
break;
case SlaveState::PHASE1:
if (phase1(now)) {
initPhase2();
slave.state = SlaveState::PHASE2;
slave.phaseStart = now;
}
break;
case SlaveState::PHASE2:
if (phase2(now)) {
initEnd();
digitalWrite(PIN_END_SIGNAL, HIGH);
slave.state = SlaveState::END;
}
break;
case SlaveState::END: {
bool master_is_high = digitalRead(PIN_MASTER_SIGNAL);
digitalWrite(PIN_END_SIGNAL, master_is_high ? HIGH : LOW);
// Rimane in END, non torna in IDLE
break;
}
}
}END_SIGNAL
MASTER_SIGNAL