#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_NeoPixel.h>
#include <ArduinoJson.h>
#include <vector> // For dynamic arrays
// Wi-Fi credentials
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// URLs to check and reset the flag, and for fetching song data and saving performance
const char* checkFlagUrl = "https:/check_flag.php";
const char* resetFlagUrl = "https:/reset_flag.php";
const char* songDataUrl = "https:/song.php";
const char* savePerformanceUrl = "https:/save_performance.php";
const char* statusUrl = "https:/esp32_status.php";
// Pin and hardware configurations
#define LED_PIN 13 // GPIO pin for the LED strip data
#define LED_COUNT 14 // Total number of LEDs in the strip
#define PIR_PIN 25 // GPIO pin for PIR motion sensor
// Define GPIO pins for each push button
const int buttonPins[LED_COUNT] = {4, 5, 12, 16, 17, 18, 19, 23, 35, 33, 26, 14, 27};
// Variables for the song data
std::vector<int> noteToLED;
std::vector<int> noteDurations;
String songName;
String mode;
int numNotes = 0;
int correctPresses = 0;
int totalPresses = 0;
// Initialize 16x4 LCD and WS2811 LED strip
LiquidCrystal_I2C lcd(0x27, 16, 4); // Set the LCD address to 0x27, for 16x4 LCD
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); // For WS2811 LED strip
// Function to send ESP32 status to the server
void sendEspStatus(String status) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(statusUrl);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String postData = "status=" + status;
int httpResponseCode = http.POST(postData);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.println("Status sent: " + status);
Serial.println("Server response: " + response);
} else {
Serial.print("Error on sending status POST: ");
Serial.println(httpResponseCode);
}
http.end();
} else {
Serial.println("WiFi Disconnected");
}
}
// Function to connect to Wi-Fi
void connectToWiFi() {
Serial.print("Connecting to WiFi");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Connecting....");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Connected");
Serial.println("Connected to WiFi!");
// Light all colors on the strip in one round after connecting to WiFi
lightUpStripColors();
}
// Function to light all colors in one round on the LED strip
void lightUpStripColors() {
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, strip.Color(255, 0, 0)); // Red
strip.show();
delay(100);
}
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, strip.Color(0, 255, 0)); // Green
strip.show();
delay(100);
}
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, strip.Color(0, 0, 255)); // Blue
strip.show();
delay(100);
}
for (int i = 0; i < LED_COUNT; i++) {
strip.setPixelColor(i, strip.Color(0, 0, 0)); // Turn off all LEDs
strip.show();
}
}
// Function to check if the ESP32 needs to fetch song data again
bool checkForSongFetch() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(checkFlagUrl);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
String flag = http.getString();
Serial.println("Flag value: " + flag);
if (flag == "true") {
return true;
}
} else {
Serial.println("Error on checking fetch flag: " + String(httpResponseCode));
}
http.end();
}
return false;
}
// Function to reset the flag on the server after fetching the song data
void resetFetchFlag() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(resetFlagUrl);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
Serial.println("Fetch flag reset successfully");
} else {
Serial.println("Error on resetting fetch flag: " + String(httpResponseCode));
}
http.end();
}
}
// Function to fetch song data from the server
void fetchSongData() {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(songDataUrl);
int httpResponseCode = http.GET();
if (httpResponseCode > 0) {
String response = http.getString();
Serial.println(httpResponseCode);
Serial.println(response);
StaticJsonDocument<1024> doc;
DeserializationError error = deserializeJson(doc, response);
if (!error) {
songName = doc["song_name"].as<String>();
mode = doc["mode"].as<String>();
JsonArray notes = doc["notes"];
JsonArray durations = doc["durations"];
numNotes = notes.size();
noteToLED.clear();
noteDurations.clear();
for (int i = 0; i < numNotes; i++) {
noteToLED.push_back(notes[i]);
noteDurations.push_back(durations[i]);
}
Serial.println("Song Name: " + songName);
Serial.println("Mode: " + mode);
Serial.print("Number of Notes: ");
Serial.println(numNotes);
} else {
Serial.println("Error parsing JSON");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Error fetching");
lcd.setCursor(0, 1);
lcd.print("data from server");
}
} else {
Serial.println("Error in HTTP request");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("HTTP Request Err");
}
http.end();
} else {
Serial.println("WiFi Disconnected");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Disconnected");
}
}
// Function to display the selected song name and mode to the user
void promptUser() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Song: " + songName);
lcd.setCursor(0, 1);
lcd.print("Mode: " + mode);
delay(2000);
}
void sendPerformanceToServer(String songName, String mode, float scorePercentage) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(savePerformanceUrl); // Replace with your server URL
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
// Prepare the data to send
String postData = "song_name=" + songName + "&mode=" + mode + "&score=" + String(scorePercentage);
// Send HTTP POST request
int httpResponseCode = http.POST(postData);
// Check the response code
if (httpResponseCode > 0) {
String response = http.getString(); // Get the response to check if it's successful
Serial.println(httpResponseCode);
Serial.println(response);
} else {
Serial.print("Error on sending POST: ");
Serial.println(httpResponseCode);
}
http.end(); // Close connection
} else {
Serial.println("WiFi Disconnected");
}
}
// Function to play the song in Tutorial Mode
void playTutorialMode() {
correctPresses = 0; // Reset score
totalPresses = numNotes; // Total notes is total presses
for (int i = 0; i < numNotes; i++) {
int ledIndex = noteToLED[i];
int duration = noteDurations[i];
// Light up the current LED
strip.setPixelColor(ledIndex, strip.Color(255, 0, 0)); // Set LED to red
strip.show();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Press Btn ");
lcd.print(ledIndex + 1); // Show which button to press
lcd.setCursor(0, 1);
lcd.print("for Note ");
lcd.print(i + 1);
// Wait until the corresponding button is pressed
bool correctButtonPressed = false;
while (!correctButtonPressed) {
if (digitalRead(buttonPins[ledIndex]) == HIGH) {
correctButtonPressed = true;
correctPresses++; // Increment score
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Correct!");
delay(1000); // Display message for 1 second
}
delay(100); // Small delay to prevent rapid loop execution
}
// Turn off the LED after button press
strip.setPixelColor(ledIndex, strip.Color(0, 0, 0)); // Turn off LED
strip.show();
delay(200); // Adjust delay as needed
}
}
// Function to play the song in Play Mode (automatic play)
void playPlayMode() {
correctPresses = 0; // Reset score
totalPresses = numNotes; // Total notes is total presses
for (int i = 0; i < numNotes; i++) {
int ledIndex = noteToLED[i];
int duration = noteDurations[i];
// Light up the current LED
strip.setPixelColor(ledIndex, strip.Color(0, 255, 0)); // Set LED to green for Play Mode
strip.show();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Playing Note ");
lcd.print(i + 1);
bool correctButtonPressed = false;
unsigned long startTime = millis();
// Wait for user input during the note duration
while (millis() - startTime < duration) {
if (digitalRead(buttonPins[ledIndex]) == HIGH) {
correctButtonPressed = true;
correctPresses++; // Increment score for correct timing
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Correct!");
break;
}
}
// Turn off the LED after the note duration
strip.setPixelColor(ledIndex, strip.Color(0, 0, 0)); // Turn off LED
strip.show();
delay(200); // Add a short delay before the next note
}
}
// Function to print performance and send it to the server
void printPerformanceOverview() {
float scorePercentage = (float(correctPresses) / float(totalPresses)) * 100.0;
// Print the performance overview
Serial.println("Performance Overview:");
Serial.println("---------------------");
Serial.print("Song: ");
Serial.println(songName);
Serial.print("Mode: ");
Serial.println(mode);
Serial.print("Score: ");
Serial.print(correctPresses);
Serial.print("/");
Serial.print(totalPresses);
Serial.print(" (");
Serial.print(scorePercentage);
Serial.println("%)");
// Send performance to server
sendPerformanceToServer(songName, mode, scorePercentage);
}
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
strip.begin();
strip.show();
// Display welcome message
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Welcome!");
lcd.setCursor(0, 1);
lcd.print("Initializing...");
delay(2000);
connectToWiFi();
sendEspStatus("on");
// Optionally send periodic "on" status updates every 5 minutes (300,000 ms)
static unsigned long lastStatusUpdate = 0;
unsigned long currentMillis = millis();
if (currentMillis - lastStatusUpdate >= 300000) { // Every 5 minutes
sendEspStatus("on");
lastStatusUpdate = currentMillis;
}
pinMode(PIR_PIN, INPUT);
for (int i = 0; i < LED_COUNT; i++) {
pinMode(buttonPins[i], INPUT_PULLDOWN);
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("System Loaded..");
lcd.setCursor(0, 1);
lcd.print("Move to Start");
delay(2000);
}
void loop() {
// Check PIR sensor state
int pirState = digitalRead(PIR_PIN);
// Print PIR sensor state to the serial monitor
if (pirState == HIGH) {
Serial.println("Motion detected!");
// Update the LCD to prompt the user to press a button
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Press Btn To Start");
lcd.setCursor(0, 1);
lcd.print("Song Sequence");
bool startSong = false;
// Check for button presses to start the song
while (!startSong) {
for (int i = 0; i < LED_COUNT; i++) {
if (digitalRead(buttonPins[i]) == HIGH) {
startSong = true;
break;
}
}
// Non-blocking delay with serial check
delay(100);
}
// Play the song in the selected mode after button press
if (mode == "tutorial") {
playTutorialMode();
} else if (mode == "play") {
playPlayMode();
}
// Print the performance overview
printPerformanceOverview();
// Display "Song Finished" message
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Song Finished");
delay(2000);
// Prompt the user to move to the start position again
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Move to Start");
delay(2000);
} else {
// No motion detected, print status
Serial.println("No motion detected.");
}
// Check if a song fetch is needed
if (checkForSongFetch()) {
fetchSongData();
promptUser();
resetFetchFlag();
}
// Add a delay to avoid flooding the serial monitor
delay(500); // Adjust the delay as needed to control sensor checks
}