#include <WiFi.h>
#include "esp_wpa2.h"
#include "time.h"
#include <ctime>
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C LCD = LiquidCrystal_I2C(0x27, 16, 2);
const char* ssid = "Wokwi-GUEST";
const char* password = "";
Servo myServo;
const uint8_t LED_PIN = 2;
const uint8_t SERVO_PIN = 18;
// This can be changed to speed up (or slow down) time.
// The timey-wimey variable.
int timeMultiplier = 1;
String displayedTime = "";
// "Now" according to our clock. The same as real-life
// "now" unless timeMultiplier has been set to != 1 at
// any point.
unsigned long currentTimeMs;
// Current loop iteration time
unsigned long currentLoopTimeMs = millis();
// Previous loop iteration time
unsigned long previousLoopTimeMs = 0;
// Define HTTP timeout time in milliseconds
const long httpTimeoutMs = 2000;
int pos1 = 0;
int pos2 = 0;
const char* NTP_SERVER = "pool.ntp.org";
const long GMT_OFFSET_SECONDS = -3600 * 4;
const int DAYLIGHT_OFFSET_SECONDS = 0;
// Web server on port 80 (http)
WiFiServer server(80);
// Variable to store the HTTP request
String header;
struct request_t {
String method;
String path;
String search;
};
enum digit_type {
HOUR,
MINUTE,
SECOND
};
String getIndexHTML() {
return R"(
<html>
<head>
<title>ESPClock</title>
<head>
<body>
<main>
<button onclick='tellTime()'>
Tell Me The Time
</button>
</main>
</body>
<script>
function tellTime() {
fetch('/tellTime').then(() => {
console.log('Telling the time');
})
}
</script>
</html>
)";
}
String respondToRequest(request_t request) {
Serial.printf("PATH: %s\n", request.path);
if (request.path == "/") {
return getIndexHTML();
}
if (request.path == "/tellTime") {
tellTime();
}
return "";
}
void setupWifi() {
//connect to WiFi
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.printf("WiFi status: %s\n", getWiFiStatusString(WiFi.status()));
delay(500);
}
Serial.println("\nCONNECTED");
}
char* getWiFiStatusString(wl_status_t status) {
switch (status) {
case WL_NO_SHIELD: return "WL_NO_SHIELD";
case WL_IDLE_STATUS: return "WL_IDLE_STATUS";
case WL_NO_SSID_AVAIL: return "WL_NO_SSID_AVAIL";
case WL_SCAN_COMPLETED: return "WL_SCAN_COMPLETED";
case WL_CONNECTED: return "WL_CONNECTED";
case WL_CONNECT_FAILED: return "WL_CONNECT_FAILED";
case WL_CONNECTION_LOST: return "WL_CONNECTION_LOST";
case WL_DISCONNECTED: return "WL_DISCONNECTED";
}
}
request_t parseRequest(String request) {
int spaceIndex = request.indexOf(' ');
String method = request.substring(0, spaceIndex);
int nextSpaceIndex = request.indexOf(' ', spaceIndex + 1);
String path = request.substring(spaceIndex + 1, nextSpaceIndex);
int questionMarkIndex = request.indexOf('?');
String searchString = "";
if (questionMarkIndex >= 0) {
path = path.substring(0, questionMarkIndex);
searchString = path.substring(questionMarkIndex + 1, path.length());
}
return {
.method = method,
.path = path,
.search = searchString,
};
}
void serveHttp() {
// Listen for incoming clients
WiFiClient client = server.available();
// Client Connected
if (client) {
Serial.println("New Client.");
// String to hold data from client
String currentLine = "";
// Do while client is connected
while (client.connected() && currentLoopTimeMs - previousLoopTimeMs <= httpTimeoutMs) {
currentLoopTimeMs = millis();
if (client.available()) {
char c = client.read();
Serial.write(c);
header += c;
if (c == '\n') {
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
request_t request = parseRequest(header);
Serial.printf("request: %s\n", request);
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK) and a content-type
client.println(R"(
HTTP/1.1 200 OK
Content-type:text/html
Connection: close
)");
// HTML Header
client.println(respondToRequest(request));
// The HTTP response ends with another blank line
client.println();
// Break out of the while loop
break;
} else {
// New line is received, clear currentLine
currentLine = "";
}
} else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
}
}
// Clear the header variable
header = "";
// Close the connection
client.stop();
Serial.println("Client disconnected.");
Serial.println("");
}
}
String formatTime(struct tm *timeInfo) {
char timeString [9];
strftime(timeString, sizeof(timeString), "%T", timeInfo);
return timeString;
}
void blink(uint32_t duration) {
Serial.println("***blink***");
digitalWrite(LED_PIN, HIGH);
delay(duration);
digitalWrite(LED_PIN, LOW);
}
void outputZero() {
uint32_t blinkDuration = 150;
uint32_t delayDuration = 50;
int numBlinks = 3;
for (int i = 0; i < numBlinks; i++) {
blink(blinkDuration);
if (i < numBlinks - 1) {
delay(delayDuration);
}
}
}
void outputSegmentSeparator() {
delay(2000);
}
void outputDigitSeparator() {
delay(1000);
}
/**
* Makes the clock represent a single digit of the time.
*/
void outputDigit(int digit, digit_type type) {
char* typeString;
uint8_t brightness;
if (type == HOUR) {
typeString = "HOUR";
brightness = 255;
}
else if (type == MINUTE) {
typeString = "MINUTE";
brightness = 128;
}
else if (type == SECOND) {
typeString = "SECOND";
brightness = 62;
}
else {
typeString = "??UNKOWN??";
brightness = 20;
}
for (int i = 0; i < digit; i++) {
analogWrite(LED_PIN, brightness);
delay(500);
analogWrite(LED_PIN, LOW);
delay(50);
}
}
/**
* Makes the clock represent a time part pair
* (i.e. HH, MM, SS).
*/
void outputPair(String pair, digit_type type) {
if (pair == "00") {
outputZero();
}
else {
int digit1 = pair[0] - '0';
int digit2 = pair[1] - '0';
outputDigit(digit1, type);
outputDigitSeparator();
outputDigit(digit2, type);
}
}
/**
* Triggers the clock to do its thing and tell the
* current time.
*/
void tellTime() {
String time = getClockCurrentTimeString();
Serial.printf("Current time: %s\n", time);
// Convert digit chars to digits (e.g. "3" -> 3).
// Format is "HH:MM:SS".
String hours = time.substring(0, 2);
String minutes = time.substring(3, 5);
String seconds = time.substring(6, 8);
outputPair(hours, HOUR);
outputSegmentSeparator();
outputPair(minutes, MINUTE);
outputSegmentSeparator();
outputPair(seconds, SECOND);
}
/**
* Gets a formatted string representing what
* the clock thinks the current time is.
*/
String getClockCurrentTimeString() {
// time_t is in seconds, not milliseconds
time_t time = currentTimeMs / 100;
struct tm timeInfo = *localtime(&time);
return formatTime(&timeInfo);
}
void displayTimeOnLCD() {
String timeString = getClockCurrentTimeString();
Serial.printf("displaying time on LCD: %s\n", timeString);
if (displayedTime == "") {
LCD.print(timeString);
}
else {
for (int i = 0; i < 8; i++) {
Serial.printf(
"index: %d\ndisplayedTime[i]: %s\ntimeString[i]: %s\n",
i,
displayedTime[i],
timeString[i]
);
if (displayedTime[i] != timeString[i]) {
LCD.setCursor(0, i);
LCD.print(timeString[i]);
}
}
}
// LCD.println(timeString);
displayedTime = timeString;
}
/**
* Sets the initial timestamp for the clock--i.e. the time
* from which it starts tracking the time.
*/
void setInitialTimestamp() {
configTime(GMT_OFFSET_SECONDS, DAYLIGHT_OFFSET_SECONDS, NTP_SERVER);
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
throw "Failed to obtain time";
}
currentTimeMs = mktime(&timeinfo);
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("INITIALIZING");
pinMode(LED_PIN, OUTPUT);
// pinMode(SERVO_PIN, OUTPUT)
// myServo.attach(SERVO_PIN);
// myServo.write(0);
setupWifi();
setInitialTimestamp();
LCD.init();
displayTimeOnLCD();
Serial.printf("Current time (ms): %d\n", currentTimeMs);
// tellTime();
Serial.print("Server: http://");
Serial.print(WiFi.localIP());
Serial.println();
server.begin();
}
void loop() {
// put your main code here, to run repeatedly:
currentLoopTimeMs = millis();
unsigned long msSinceLastLoop = currentLoopTimeMs - previousLoopTimeMs;
previousLoopTimeMs = currentLoopTimeMs;
currentTimeMs = currentTimeMs + msSinceLastLoop * timeMultiplier;
displayTimeOnLCD();
serveHttp();
// myServo.write(90);
// delay(500);
// myServo.write(0);
// delay(500);
}