// Include necessary libraries for ESP32, I2C LCD, Wi-Fi, NTP, and Web Server
#include <WiFi.h>
#include <NTPClient.h> // For getting time from NTP server
#include <WiFiUdp.h> // Required by NTPClient
#include <WebServer.h> // For running a simple web server
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// --- Wi-Fi Configuration ---
const char* WIFI_SSID = "SEM Internet"; // <<--- CHANGE THIS to your Wi-Fi SSID
const char* WIFI_PASSWORD = "Gtfy89-w!"; // <<--- CHANGE THIS to your Wi-Fi password
// --- LCD Configuration ---
const int LCD_ADDRESS = 0x27; // I2C address of your LCD (common: 0x27 or 0x3F)
const int LCD_COLUMNS = 16; // Number of columns on your LCD
const int LCD_ROWS = 2; // Number of rows on your LCD
// --- Button Configuration ---
const int BUTTON_PIN = 14; // GPIO 14 for the mode change button
// --- Time Configuration (NTP) ---
const long GMT_OFFSET_SECONDS = 3600; // Offset in seconds from GMT (e.g., 3600 for +1 hour, 0 for GMT)
const char* NTP_SERVER = "pool.ntp.org"; // NTP server to use
// --- Global Objects ---
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS); // LCD object
WiFiUDP ntpUdp; // UDP client for NTP
NTPClient timeClient(ntpUdp, NTP_SERVER, GMT_OFFSET_SECONDS); // NTP client
WebServer server(80); // Web server on port 80
// --- State Variables ---
int buttonState; // Current reading from the button pin
int lastButtonState = HIGH; // Previous reading from the button pin
unsigned long lastDebounceTime = 0; // The last time the output pin was toggled
const unsigned long debounceDelay = 50; // Debounce time; increase if button is jumpy
// Enum for different display modes
enum DisplayMode {
MODE_NOTES,
MODE_TIME
};
DisplayMode currentMode = MODE_NOTES; // Start in notes mode
// --- Note Display & Scrolling Variables ---
// This note can be updated via the web interface
String userNote = "Hello from ESP32! Change me via web!";
String displayingNote = ""; // The note currently being displayed (for scrolling)
int scrollPosition = 0;
unsigned long lastScrollTime = 0;
const unsigned long scrollDelay = 300; // Delay between scroll steps (milliseconds)
// --- Function Prototypes ---
void connectWiFi();
void handleRoot();
void handleNoteSubmit();
void setDisplayingNote(String note);
void scrollText();
void displayTime();
/**
* @brief Sets the note to be displayed and resets scrolling.
* This function is called when a new note is set (e.g., from web server)
* or when switching back to notes mode.
* @param note The string to set as the current note.
*/
void setDisplayingNote(String note) {
displayingNote = note;
scrollPosition = 0; // Reset scroll position for the new note
lcd.clear(); // Clear the LCD when a new note is set
}
/**
* @brief Handles the horizontal scrolling of the current note on the LCD.
* This function should be called repeatedly in the loop when in MODE_NOTES.
*/
void scrollText() {
if (millis() - lastScrollTime > scrollDelay) {
lastScrollTime = millis();
// If the note is shorter than or equal to the LCD width, just display it once
if (displayingNote.length() <= LCD_COLUMNS) {
lcd.setCursor(0, 0);
lcd.print(displayingNote);
lcd.setCursor(0, 1);
lcd.print(" "); // Clear second line
return;
}
// Calculate the portion of the string to display
// Add some spaces at the end for smooth looping before the text repeats
String textToScroll = displayingNote;
textToScroll += " "; // Add padding for smooth loop
// Get the substring for the current scroll position
String line1Content = "";
for (int i = 0; i < LCD_COLUMNS; i++) {
if ((scrollPosition + i) < textToScroll.length()) {
line1Content += textToScroll[scrollPosition + i];
} else {
line1Content += ' '; // Fill with spaces if end of string reached
}
}
lcd.setCursor(0, 0);
lcd.print(line1Content);
// Clear the second line (or display static info if desired)
lcd.setCursor(0, 1);
lcd.print(" ");
scrollPosition++;
// Reset scroll position if we've scrolled past the end of the note (plus padding)
if (scrollPosition >= textToScroll.length()) {
scrollPosition = 0;
}
}
}
/**
* @brief Displays the current time (fetched from NTP) on the LCD.
* This function is called when in MODE_TIME.
*/
void displayTime() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Current Time:");
lcd.setCursor(0, 1);
// Update NTP client to get the latest time
timeClient.update();
// Get formatted time string (e.g., "HH:MM:SS")
String formattedTime = timeClient.getFormattedTime();
lcd.print(formattedTime);
// You can also display date or day of week if you want
// lcd.setCursor(0, 1);
// lcd.print(timeClient.getDate()); // Requires custom function to format date
}
/**
* @brief Connects the ESP32 to the configured Wi-Fi network.
*/
void connectWiFi() {
Serial.print("Connecting to WiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Connecting WiFi");
lcd.setCursor(0,1);
lcd.print("Please wait...");
}
Serial.println("\nWiFi Connected!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
lcd.clear();
lcd.setCursor(0,0);
lcd.print("WiFi Connected!");
lcd.setCursor(0,1);
lcd.print(WiFi.localIP());
delay(2000); // Display IP for a moment
}
/**
* @brief Handles requests to the root URL ("/").
* Serves an HTML form to input a new note.
*/
void handleRoot() {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<title>ESP32 LCD Note Setter</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f0f0; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
h1 { color: #0056b3; text-align: center; margin-bottom: 20px; }
form { display: flex; flex-direction: column; }
label { margin-bottom: 8px; font-weight: bold; }
textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; resize: vertical; min-height: 80px; box-sizing: border-box; }
input[type="submit"] {
background-color: #007bff;
color: white;
padding: 12px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s ease;
}
input[type="submit"]:hover { background-color: #0056b3; }
.current-note { margin-top: 20px; padding: 15px; background-color: #e9ecef; border-left: 5px solid #007bff; border-radius: 4px; }
.current-note strong { color: #0056b3; }
.ip-address { text-align: center; margin-top: 30px; font-size: 0.9em; color: #666; }
</style>
</head>
<body>
<div class="container">
<h1>Set LCD Note</h1>
<div class="current-note">
<strong>Current Note on LCD:</strong><br>
<span id="displayNote">)rawliteral";
html += userNote; // Insert the current note here
html += R"rawliteral(</span>
</div>
<form action="/submitNote" method="POST">
<label for="note">Enter your note:</label>
<textarea id="note" name="note" maxlength="100" placeholder="Type your message here (max 100 chars)"></textarea>
<input type="submit" value="Update LCD Note">
</form>
<p class="ip-address">Access this page at: http://)rawliteral";
html += WiFi.localIP().toString();
html += R"rawliteral(</p>
</div>
</body>
</html>
)rawliteral";
server.send(200, "text/html", html);
}
/**
* @brief Handles the form submission for a new note.
* Updates the `userNote` and redirects back to the root.
*/
void handleNoteSubmit() {
if (server.hasArg("note")) {
userNote = server.arg("note");
// Ensure the note is not too long for the LCD's memory or display logic
if (userNote.length() > 100) { // Max length for the web form
userNote = userNote.substring(0, 100);
}
Serial.print("New note received: ");
Serial.println(userNote);
// Immediately update the displaying note if in notes mode
if (currentMode == MODE_NOTES) {
setDisplayingNote(userNote);
}
}
server.sendHeader("Location", "/"); // Redirect back to the root page
server.send(303); // See Other
}
/**
* @brief Initializes the LCD, serial communication, button pin,
* Wi-Fi connection, NTP client, and web server.
* This function runs once when the ESP32 starts.
*/
void setup() {
// Initialize serial communication
Serial.begin(115200);
Serial.println("ESP32 I2C LCD Note Display with Scrolling, Time, and Web Control");
// Initialize the LCD
lcd.init();
lcd.backlight();
lcd.print("Starting up...");
lcd.setCursor(0,1);
lcd.print("Please wait.");
// Set button pin as input with pullup resistor
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Connect to Wi-Fi
connectWiFi();
// Initialize NTP client
timeClient.begin();
// Ensure the time is updated at least once at startup
timeClient.update();
// Setup web server routes
server.on("/", HTTP_GET, handleRoot);
server.on("/submitNote", HTTP_POST, handleNoteSubmit);
server.begin(); // Start the web server
Serial.println("HTTP server started");
// Set the initial note to display
setDisplayingNote(userNote);
Serial.println("Ready for notes and time display.");
}
/**
* @brief The main loop function.
* This function runs repeatedly after setup(). It handles button presses,
* mode switching, display updates (notes scrolling or time display),
* and web server client requests.
*/
void loop() {
// Handle web server client requests
server.handleClient();
// Read the state of the button
int reading = digitalRead(BUTTON_PIN);
// If the switch changed, due to noise or pressing:
if (reading != lastButtonState) {
// Reset the debouncing timer
lastDebounceTime = millis();
}
// Check if the button state has been stable for the debounce delay
if ((millis() - lastDebounceTime) > debounceDelay) {
// Whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual button state.
if (reading != buttonState) {
buttonState = reading;
// Only act if the button is pressed (LOW, since using INPUT_PULLUP)
if (buttonState == LOW) {
// Toggle the display mode
if (currentMode == MODE_NOTES) {
currentMode = MODE_TIME;
Serial.println("Switched to TIME_MODE");
displayTime(); // Immediately update LCD with time
} else {
currentMode = MODE_NOTES;
Serial.println("Switched to NOTE_MODE");
// When switching back to notes, set the current note
setDisplayingNote(userNote);
}
}
}
}
// Handle display based on current mode
if (currentMode == MODE_NOTES) {
scrollText(); // Continuously scroll the current note
} else { // MODE_TIME
// Update time display every second
// This static variable ensures the time is only updated when needed to avoid flickering
static unsigned long lastTimeUpdate = 0;
if (millis() - lastTimeUpdate > 1000) { // Update every second
lastTimeUpdate = millis();
displayTime();
}
}
// Save the current button reading for the next iteration
lastButtonState = reading;
}