#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <LiquidCrystal_I2C.h>
// ==========================================
// 1. CONFIGURATION
// ==========================================
const char* ssid = "css_STEAM";
const char* password = ""; // Update this!
// Pins
#define I2C_SDA 7
#define I2C_SCL 8
#define SERVO_PIN 22
// Servo Settings (ESP32 v3.0+)
#define PWM_FREQ 50
#define PWM_RES 16
#define MIN_DUTY 1638 // 0 degrees
#define MAX_DUTY 8191 // 180 degrees
// Weather API (Hong Kong Coordinates)
const String weatherURL = "https://api.open-meteo.com/v1/forecast?latitude=22.31&longitude=114.16¤t_weather=true";
// Objects
LiquidCrystal_I2C lcd(0x27, 16, 2);
WebServer server(80);
// Global Variables
float currentTemp = 0.0;
String ipAddress = "0.0.0.0";
// ==========================================
// 2. HELPER FUNCTIONS
// ==========================================
// Move Servo to specific angle
void setServo(int angle) {
angle = constrain(angle, 0, 180);
uint32_t duty = map(angle, 0, 180, MIN_DUTY, MAX_DUTY);
ledcWrite(SERVO_PIN, duty);
}
// Fetch Weather from Open-Meteo
void updateWeather() {
HTTPClient http;
http.begin(weatherURL);
int httpCode = http.GET();
if (httpCode == 200) {
String payload = http.getString();
// Manual String Parsing (Simpler than installing ArduinoJson)
// We look for "temperature":
int tempIndex = payload.indexOf("\"temperature\":");
if (tempIndex > 0) {
int startIndex = tempIndex + 14;
int endIndex = payload.indexOf(",", startIndex);
String tempString = payload.substring(startIndex, endIndex);
currentTemp = tempString.toFloat();
Serial.println("Weather Updated: " + String(currentTemp) + "C");
}
} else {
Serial.println("Weather Error: " + String(httpCode));
}
http.end();
}
// Scroll Text on LCD Line 1 (Line 0 is top, Line 1 is bottom)
void scrollText(String message, int row, int delayTime) {
for (int i = 0; i < message.length() - 15; i++) {
lcd.setCursor(0, row);
lcd.print(message.substring(i, i + 16));
delay(delayTime);
}
}
// ==========================================
// 3. WEB HANDLERS
// ==========================================
// ROOT PAGE: The HTML Dashboard
void handleRoot() {
String html = "<!DOCTYPE html><html><head><meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>body{font-family:sans-serif;text-align:center;margin-top:50px;}";
html += "button{padding:15px 30px;font-size:20px;margin:10px;border-radius:10px;border:none;background:#007BFF;color:white;}";
html += "button:active{background:#0056b3;}</style></head><body>";
html += "<h1>ESP32 Control</h1>";
html += "<p>Current HK Temp: <b>" + String(currentTemp) + "°C</b></p>";
html += "<a href='/set?angle=0'><button>0°</button></a>";
html += "<a href='/set?angle=90'><button>90°</button></a>";
html += "<a href='/set?angle=180'><button>180°</button></a><br>";
html += "<a href='/swing'><button style='background:#28a745'>SWING!</button></a>";
html += "</body></html>";
server.send(200, "text/html", html);
}
// API: Set Angle (Usage: /set?angle=45)
void handleSetAngle() {
if (server.hasArg("angle")) {
int angle = server.arg("angle").toInt();
setServo(angle);
server.send(200, "text/plain", "OK: Angle set to " + String(angle));
Serial.println("API Request: Set Angle " + String(angle));
} else {
server.send(400, "text/plain", "Error: Missing 'angle' parameter");
}
}
// API: Swing (Usage: /swing)
void handleSwing() {
server.send(200, "text/plain", "OK: Swinging...");
Serial.println("API Request: Swinging");
// Simple swing animation
for(int i=0; i<=180; i+=5) { setServo(i); delay(15); }
for(int i=180; i>=0; i-=5) { setServo(i); delay(15); }
setServo(90); // Return to center
}
// ==========================================
// 4. MAIN SETUP
// ==========================================
void setup() {
Serial.begin(115200);
// 1. Setup LCD
Wire.begin(I2C_SDA, I2C_SCL);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("System Booting..");
// 2. Setup Servo
if (!ledcAttach(SERVO_PIN, PWM_FREQ, PWM_RES)) {
Serial.println("Servo Attach Failed");
}
setServo(0); // Start at 0
// 3. Connect to WiFi
WiFi.begin(ssid, password);
lcd.setCursor(0, 1);
lcd.print("Connecting WiFi ");
int dots = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.print(".");
dots++;
if (dots > 5) {
dots = 0;
lcd.setCursor(15, 1);
lcd.print(" "); // Clear dots
lcd.setCursor(15, 1);
}
// If it takes too long (> 20 seconds), scroll error
if (millis() > 20000 && WiFi.status() != WL_CONNECTED) {
lcd.clear();
lcd.print("WiFi Error!");
scrollText("Check SSID/Password... Retrying... ", 1, 300);
}
}
// 4. Connection Success
ipAddress = WiFi.localIP().toString();
Serial.println("\nWiFi Connected! IP: " + ipAddress);
// 5. Initial Weather Fetch
updateWeather();
// 6. Update LCD with Info
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("IP:");
lcd.print(ipAddress);
lcd.setCursor(0, 1);
lcd.print("HK:");
lcd.print(currentTemp, 1); // 1 decimal place
lcd.print("C Running");
// 7. Start Web Server
server.on("/", handleRoot);
server.on("/set", handleSetAngle);
server.on("/swing", handleSwing);
server.begin();
}
void loop() {
server.handleClient();
// Optional: Refresh weather every 10 minutes
static unsigned long lastWeatherTime = 0;
if (millis() - lastWeatherTime > 600000) {
updateWeather();
lcd.setCursor(0, 1);
lcd.print("HK:");
lcd.print(currentTemp, 1);
lcd.print("C ");
lastWeatherTime = millis();
}
}