/*
* ATTRACTION SYSTEM - ESP32
* Modos: ATTRACTION, AVAILABLE, GOOD
* Servos MG996R | NEMA 17 | Motor para-brisa | SD Card
* Funciona no Wokwi (display texto) e Hardware Real
*/
#include <ESP32Servo.h>
#include <AccelStepper.h>
#include <TFT_eSPI.h>
#include <SD.h>
#include <SPI.h>
// ============== PINAGEM COMPLETA ==============
#define LED1 12
#define LED2 13
#define LED3 14
#define LED4 15
#define LED5 16
#define LED6 17
// ===== Botões =====
#define BUTTON1 26
#define BUTTON2 27
// ===== Sensores PIR (HC-SR501) =====
#define SENSOR1 32
#define SENSOR2 33
#define SENSOR3 34
#define SENSOR4 35
#define SENSOR5 36
#define SENSOR6 39
#define SENSOR7 4
#define SENSOR8 21
// ===== Main Motor (Relé) =====
#define MAIN_MOTOR_RELAY 22
// ===== NEMA 17 - Turn Motor =====
#define TURN_STEP 25
#define TURN_DIR 26
// ===== NEMA 17 - Push Motor =====
#define PUSH_STEP 18
#define PUSH_DIR 19
// ===== Servos MG996R =====
#define SERVO_180_PIN 23
#define SERVO_90_PIN 5
// ===== SD Card - Usando SPI2 (VSPI) =====
#define SD_CS 15 // Chip Select
#define SD_MOSI 13 // Master Out Slave In
#define SD_MISO 12 // Master In Slave Out
#define SD_SCK 14 // Serial Clock
// ============== OBJETOS ==============
Servo servo180;
Servo servo90;
AccelStepper turnMotor(AccelStepper::DRIVER, TURN_STEP, TURN_DIR);
AccelStepper pushMotor(AccelStepper::DRIVER, PUSH_STEP, PUSH_DIR);
TFT_eSPI tft = TFT_eSPI();
// ============== VARIÁVEIS GLOBAIS ==============
enum SystemMode { MODE_ATTRACTION, MODE_AVAILABLE, MODE_GOOD };
SystemMode currentMode = MODE_ATTRACTION;
// Timers
unsigned long mainMotorStartTime = 0;
unsigned long mainMotorStopTime = 0;
bool mainMotorRunning = false;
bool mainMotorWaiting = false;
unsigned long availableMainStartTime = 0;
bool availableMainActive = false;
unsigned long turnMotorStartTime = 0;
bool turnMotorActive = false;
unsigned long pushMotorStartTime = 0;
bool pushMotorActive = false;
unsigned long lastButtonPress = 0;
const unsigned long DEBOUNCE_DELAY = 200;
// AVAILABLE MODE
bool sensorsActivated[6] = {false, false, false, false, false, false};
// GOOD MODE
unsigned long goodModeStartTime = 0;
bool good180Done = false;
bool good90Done = false;
unsigned long lastFlashTime = 0;
bool flashState = false;
// Display timeout para overlays
unsigned long overlayEndTime = 0;
String currentOverlay = "";
// ============== PROTÓTIPOS ==============
void setupPins();
void setupSD();
void updateDisplay(const char* modeText, const char* videoFile);
void showOverlay(const char* imageFile);
void updateAttractionMode();
void updateAvailableMode();
void updateGoodMode();
void checkModeChange();
void controlTurnMotor();
void controlPushMotor();
bool isSensorActivated(int pin);
void setAllLEDs(bool state);
void flashLEDs();
void stopAllMotors();
// ============== SETUP ==============
void setup() {
Serial.begin(115200);
Serial.println("Iniciando ATTRACTION SYSTEM...");
setupPins();
// Servos MG996R
servo180.attach(SERVO_180_PIN);
servo90.attach(SERVO_90_PIN);
servo180.write(90);
servo90.write(0);
// NEMA 17
turnMotor.setMaxSpeed(1000);
turnMotor.setSpeed(600);
turnMotor.setAcceleration(400);
pushMotor.setMaxSpeed(1000);
pushMotor.setSpeed(600);
pushMotor.setAcceleration(400);
// Display
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(2);
// SD Card
setupSD();
// Iniciar no modo ATTRACTION
currentMode = MODE_ATTRACTION;
setAllLEDs(true);
updateDisplay("ATTRACTION MODE", "Attraction.mp4");
Serial.println("Setup concluído!");
}
void setupPins() {
// LEDs
pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT);
pinMode(LED4, OUTPUT); pinMode(LED5, OUTPUT); pinMode(LED6, OUTPUT);
// Botões e sensores
pinMode(BUTTON1, INPUT_PULLUP);
pinMode(BUTTON2, INPUT_PULLUP);
pinMode(SENSOR1, INPUT_PULLUP);
pinMode(SENSOR2, INPUT_PULLUP);
pinMode(SENSOR3, INPUT_PULLUP);
pinMode(SENSOR4, INPUT_PULLUP);
pinMode(SENSOR5, INPUT_PULLUP);
pinMode(SENSOR6, INPUT_PULLUP);
pinMode(SENSOR7, INPUT_PULLUP);
pinMode(SENSOR8, INPUT_PULLUP);
// Main Motor (Relé)
pinMode(MAIN_MOTOR_RELAY, OUTPUT);
digitalWrite(MAIN_MOTOR_RELAY, LOW);
stopAllMotors();
}
void setupSD() {
SPI.begin(18, 19, 23, SD_CS);
if (!SD.begin(SD_CS)) {
Serial.println("SD Card: ERRO ou ausente");
tft.setCursor(0, 200);
tft.setTextColor(TFT_RED, TFT_BLACK);
tft.println("SD Card: NOT FOUND");
return;
}
Serial.println("SD Card: OK");
// Listar arquivos para debug
File root = SD.open("/");
while (File file = root.openNextFile()) {
Serial.print("Arquivo: ");
Serial.println(file.name());
}
}
void updateDisplay(const char* modeText, const char* videoFile) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setTextSize(3);
tft.setCursor(10, 10);
tft.println(modeText);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(10, 60);
tft.print("Video: ");
tft.println(videoFile);
tft.setTextSize(1);
tft.setCursor(10, 120);
tft.println("Sensores 1-6: ");
for (int i = 0; i < 6; i++) {
tft.print(sensorsActivated[i] ? "[X]" : "[ ]");
if (i == 2) tft.println();
}
Serial.print("Display: ");
Serial.print(modeText);
Serial.print(" - ");
Serial.println(videoFile);
}
void showOverlay(const char* imageFile) {
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(10, 180);
tft.print("Overlay: ");
tft.println(imageFile);
Serial.print("Overlay: ");
Serial.println(imageFile);
overlayEndTime = millis() + 1500;
}
// ============== LOOP PRINCIPAL ==============
void loop() {
checkModeChange();
// Remover overlay após timeout
if (overlayEndTime > 0 && millis() > overlayEndTime) {
overlayEndTime = 0;
if (currentMode == MODE_ATTRACTION)
updateDisplay("ATTRACTION MODE", "Attraction.mp4");
else if (currentMode == MODE_AVAILABLE)
updateDisplay("AVAILABLE MODE", "Available.mp4");
else if (currentMode == MODE_GOOD)
updateDisplay("GOOD MODE", "Good.mp4");
}
// Atualizar motores NEMA 17
turnMotor.runSpeedToPosition();
pushMotor.runSpeedToPosition();
switch(currentMode) {
case MODE_ATTRACTION:
updateAttractionMode();
break;
case MODE_AVAILABLE:
updateAvailableMode();
break;
case MODE_GOOD:
updateGoodMode();
break;
}
controlTurnMotor();
controlPushMotor();
delay(10);
}
// ============== MODO ATTRACTION ==============
void updateAttractionMode() {
static unsigned long lastServoMove = 0;
static bool servoPos = false;
unsigned long now = millis();
if (now - lastServoMove >= 2000) {
lastServoMove = now;
servoPos = !servoPos;
servo180.write(servoPos ? 0 : 60);
Serial.print("Servo 180°: ");
Serial.println(servoPos ? -30 : 30);
}
if (!mainMotorRunning && !mainMotorWaiting) {
mainMotorRunning = true;
mainMotorStartTime = now;
digitalWrite(MAIN_MOTOR_RELAY, HIGH);
Serial.println("Main Motor LIGADO (2 min)");
}
if (mainMotorRunning && (now - mainMotorStartTime >= 120000)) {
digitalWrite(MAIN_MOTOR_RELAY, LOW);
mainMotorRunning = false;
mainMotorWaiting = true;
mainMotorStopTime = now;
Serial.println("Main Motor DESLIGADO (4 min)");
}
if (mainMotorWaiting && (now - mainMotorStopTime >= 240000)) {
mainMotorWaiting = false;
}
}
// ============== MODO AVAILABLE ==============
void updateAvailableMode() {
setAllLEDs(true);
static unsigned long lastServoMove = 0;
static bool servoPos = false;
unsigned long now = millis();
if (now - lastServoMove >= 2000) {
lastServoMove = now;
servoPos = !servoPos;
servo180.write(servoPos ? 0 : 60);
}
const int sensorPins[6] = {SENSOR1, SENSOR2, SENSOR3, SENSOR4, SENSOR5, SENSOR6};
const char* images[6] = {"/P.jpg", "/R.jpg", "/E.jpg", "/M.jpg", "/I.jpg", "/O.jpg"};
for (int i = 0; i < 6; i++) {
if (isSensorActivated(sensorPins[i]) && !sensorsActivated[i]) {
sensorsActivated[i] = true;
showOverlay(images[i]);
updateDisplay("AVAILABLE MODE", "Available.mp4");
Serial.printf("Sensor %d ativado!\n", i+1);
}
}
bool allActivated = true;
for (int i = 0; i < 6; i++) {
if (!sensorsActivated[i]) { allActivated = false; break; }
}
if (allActivated) {
Serial.println(">>> GOOD MODE ATIVADO! <<<");
currentMode = MODE_GOOD;
updateDisplay("GOOD MODE", "Good.mp4");
return;
}
if (!availableMainActive) {
availableMainActive = true;
availableMainStartTime = now;
digitalWrite(MAIN_MOTOR_RELAY, HIGH);
Serial.println("Main Motor LIGADO (1 min)");
}
if (digitalRead(BUTTON1) == LOW || digitalRead(BUTTON2) == LOW) {
if (now - lastButtonPress > DEBOUNCE_DELAY) {
lastButtonPress = now;
availableMainStartTime = now;
Serial.println("Timer do Main Motor resetado!");
}
}
if (availableMainActive && (now - availableMainStartTime >= 60000)) {
digitalWrite(MAIN_MOTOR_RELAY, LOW);
availableMainActive = false;
Serial.println("Main Motor DESLIGADO");
}
static unsigned long servo90Start = 0;
static bool servo90Active = false;
if (!servo90Active) {
servo90.write(90);
servo90Active = true;
servo90Start = now;
Serial.println("Servo 90° LIGADO (30s)");
}
if (servo90Active && (now - servo90Start >= 30000)) {
servo90.write(0);
servo90Active = false;
Serial.println("Servo 90° DESLIGADO");
}
}
// ============== MODO GOOD ==============
void updateGoodMode() {
flashLEDs();
unsigned long now = millis();
if (!mainMotorRunning) {
mainMotorRunning = true;
goodModeStartTime = now;
digitalWrite(MAIN_MOTOR_RELAY, HIGH);
Serial.println("GOOD: Main Motor LIGADO (1 min)");
}
if (mainMotorRunning && (now - goodModeStartTime >= 60000)) {
digitalWrite(MAIN_MOTOR_RELAY, LOW);
mainMotorRunning = false;
}
if (!good180Done) {
servo180.write(180);
good180Done = true;
goodModeStartTime = now;
Serial.println("GOOD: Servo 180° -> 180°");
}
if (good180Done && (now - goodModeStartTime >= 10000)) {
servo180.write(90);
Serial.println("GOOD: Servo 180° voltou");
}
if (!good90Done) {
servo90.write(90);
good90Done = true;
Serial.println("GOOD: Servo 90° LIGADO (1 min)");
}
}
// ============== FUNÇÕES DE CONTROLE ==============
void checkModeChange() {
unsigned long now = millis();
if ((digitalRead(BUTTON1) == LOW || digitalRead(BUTTON2) == LOW) &&
currentMode == MODE_ATTRACTION) {
if (now - lastButtonPress > DEBOUNCE_DELAY) {
lastButtonPress = now;
currentMode = MODE_AVAILABLE;
for (int i = 0; i < 6; i++) sensorsActivated[i] = false;
availableMainActive = false;
digitalWrite(MAIN_MOTOR_RELAY, LOW);
updateDisplay("AVAILABLE MODE", "Available.mp4");
Serial.println(">>> AVAILABLE MODE <<<");
}
}
if ((digitalRead(BUTTON1) == LOW || digitalRead(BUTTON2) == LOW) &&
currentMode == MODE_GOOD) {
if (now - lastButtonPress > DEBOUNCE_DELAY) {
lastButtonPress = now;
currentMode = MODE_ATTRACTION;
setAllLEDs(true);
good180Done = false;
good90Done = false;
mainMotorRunning = false;
digitalWrite(MAIN_MOTOR_RELAY, LOW);
updateDisplay("ATTRACTION MODE", "Attraction.mp4");
Serial.println(">>> VOLTANDO PARA ATTRACTION MODE <<<");
}
}
}
void controlTurnMotor() {
if (isSensorActivated(SENSOR7) && !turnMotorActive) {
turnMotor.move(300);
turnMotorActive = true;
turnMotorStartTime = millis();
Serial.println("Turn Motor LIGADO (5s)");
}
if (turnMotorActive && turnMotor.distanceToGo() == 0) {
turnMotorActive = false;
Serial.println("Turn Motor DESLIGADO");
}
}
void controlPushMotor() {
if (isSensorActivated(SENSOR8) && !pushMotorActive) {
pushMotor.move(350);
pushMotorActive = true;
pushMotorStartTime = millis();
Serial.println("Push Motor LIGADO (6s)");
}
if (pushMotorActive && pushMotor.distanceToGo() == 0) {
pushMotorActive = false;
Serial.println("Push Motor DESLIGADO");
}
}
bool isSensorActivated(int pin) {
return digitalRead(pin) == HIGH;
}
void setAllLEDs(bool state) {
digitalWrite(LED1, state); digitalWrite(LED2, state);
digitalWrite(LED3, state); digitalWrite(LED4, state);
digitalWrite(LED5, state); digitalWrite(LED6, state);
}
void flashLEDs() {
unsigned long now = millis();
if (now - lastFlashTime >= 500) {
lastFlashTime = now;
flashState = !flashState;
setAllLEDs(flashState);
}
}
void stopAllMotors() {
turnMotor.stop();
pushMotor.stop();
turnMotorActive = false;
pushMotorActive = false;
digitalWrite(MAIN_MOTOR_RELAY, LOW);
servo180.write(90);
servo90.write(0);
}