// ESP32 + SSD1306 OLED + Weather API (wttr.in)
// OLED: SDA=21 SCL=22 VCC=3V3 (0x3C)
// Bilingual TR/EN, vector icons, large temp font
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASS = "";
const char* CITY = "Istanbul";
const unsigned long FETCH_INTERVAL_MS = 30UL * 60UL * 1000UL;
#define SCREEN_W 128
#define SCREEN_H 64
#define OLED_ADDR 0x3C
Adafruit_SSD1306 oled(SCREEN_W, SCREEN_H, &Wire, -1);
struct WData {
String city, descEn, descTr;
int tempC, humidity, windKmh;
unsigned long lastFetchMs;
bool valid;
};
WData w = {"", "", "", 0, 0, 0, 0, false};
bool wifiOk = false;
// ============ EN -> TR cevirisi ============
String translateWeather(const String &en) {
String e = en; e.toLowerCase();
if (e.indexOf("partly cloudy") >= 0) return "Parcali bulut";
if (e.indexOf("sunny") >= 0) return "Gunesli";
if (e.indexOf("clear") >= 0) return "Acik";
if (e.indexOf("overcast") >= 0) return "Kapali";
if (e.indexOf("cloudy") >= 0) return "Bulutlu";
if (e.indexOf("mist") >= 0) return "Sisli";
if (e.indexOf("fog") >= 0) return "Sis";
if (e.indexOf("light rain") >= 0) return "Hafif yagmur";
if (e.indexOf("heavy rain") >= 0) return "Saganak";
if (e.indexOf("rain") >= 0) return "Yagmurlu";
if (e.indexOf("drizzle") >= 0) return "Cisenti";
if (e.indexOf("snow") >= 0) return "Karli";
if (e.indexOf("thunder") >= 0) return "Firtinali";
return en;
}
// ============ Vector ikonlar (16x16 alan) ============
void drawSunIcon(int x, int y) {
oled.fillCircle(x+8, y+8, 3, SSD1306_WHITE);
oled.drawLine(x+8, y, x+8, y+2, SSD1306_WHITE);
oled.drawLine(x+8, y+13, x+8, y+15, SSD1306_WHITE);
oled.drawLine(x, y+8, x+2, y+8, SSD1306_WHITE);
oled.drawLine(x+13,y+8, x+15,y+8, SSD1306_WHITE);
oled.drawPixel(x+3, y+3, SSD1306_WHITE);
oled.drawPixel(x+12,y+3, SSD1306_WHITE);
oled.drawPixel(x+3, y+12,SSD1306_WHITE);
oled.drawPixel(x+12,y+12,SSD1306_WHITE);
}
void drawCloudIcon(int x, int y) {
oled.fillCircle(x+5, y+10, 3, SSD1306_WHITE);
oled.fillCircle(x+10, y+8, 4, SSD1306_WHITE);
oled.fillRect(x+5, y+9, 7, 5, SSD1306_WHITE);
}
void drawPartlyIcon(int x, int y) {
oled.fillCircle(x+5, y+5, 3, SSD1306_WHITE);
oled.drawLine(x+5, y, x+5, y+1, SSD1306_WHITE);
oled.drawLine(x, y+5, x+1, y+5, SSD1306_WHITE);
oled.fillCircle(x+8, y+12, 3, SSD1306_WHITE);
oled.fillCircle(x+12, y+10, 4, SSD1306_WHITE);
oled.fillRect(x+8, y+11, 7, 4, SSD1306_WHITE);
}
void drawRainIcon(int x, int y) {
oled.fillCircle(x+5, y+6, 3, SSD1306_WHITE);
oled.fillCircle(x+10,y+5, 4, SSD1306_WHITE);
oled.fillRect(x+5, y+5, 7, 4, SSD1306_WHITE);
oled.drawLine(x+4, y+11, x+3, y+15, SSD1306_WHITE);
oled.drawLine(x+8, y+11, x+7, y+15, SSD1306_WHITE);
oled.drawLine(x+12, y+11, x+11, y+15, SSD1306_WHITE);
}
void drawSnowIcon(int x, int y) {
oled.fillCircle(x+5, y+6, 3, SSD1306_WHITE);
oled.fillCircle(x+10,y+5, 4, SSD1306_WHITE);
oled.fillRect(x+5, y+5, 7, 4, SSD1306_WHITE);
oled.drawChar(x+2, y+11, '*', SSD1306_WHITE, SSD1306_BLACK, 1);
oled.drawChar(x+7, y+11, '*', SSD1306_WHITE, SSD1306_BLACK, 1);
oled.drawChar(x+12, y+11, '*', SSD1306_WHITE, SSD1306_BLACK, 1);
}
void drawThunderIcon(int x, int y) {
oled.fillCircle(x+5, y+6, 3, SSD1306_WHITE);
oled.fillCircle(x+10,y+5, 4, SSD1306_WHITE);
oled.fillRect(x+5, y+5, 7, 4, SSD1306_WHITE);
oled.drawLine(x+8, y+10, x+5, y+14, SSD1306_WHITE);
oled.drawLine(x+7, y+12, x+9, y+12, SSD1306_WHITE);
oled.drawLine(x+8, y+12, x+6, y+15, SSD1306_WHITE);
}
void drawFogIcon(int x, int y) {
for (int i = 2; i <= 14; i += 3) {
oled.drawLine(x+1, y+i, x+14, y+i, SSD1306_WHITE);
}
}
void drawWeatherIcon(int x, int y, const String &en) {
String e = en; e.toLowerCase();
if (e.indexOf("partly cloudy") >= 0) { drawPartlyIcon(x, y); return; }
if (e.indexOf("sunny") >= 0 || e.indexOf("clear") >= 0) { drawSunIcon(x, y); return; }
if (e.indexOf("snow") >= 0) { drawSnowIcon(x, y); return; }
if (e.indexOf("thunder") >= 0) { drawThunderIcon(x, y); return; }
if (e.indexOf("rain") >= 0 || e.indexOf("drizzle") >= 0) { drawRainIcon(x, y); return; }
if (e.indexOf("mist") >= 0 || e.indexOf("fog") >= 0) { drawFogIcon(x, y); return; }
drawCloudIcon(x, y);
}
// ============ Ekran cizimleri ============
void drawSplash() {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(2);
oled.setCursor(8, 12);
oled.print("BlueGrays");
oled.setTextSize(1);
oled.setCursor(18, 40);
oled.print("Weather v1.0");
oled.drawRoundRect(2, 2, 124, 60, 6, SSD1306_WHITE);
oled.display();
}
void drawStatus(const char* tr, const char* en) {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
oled.setCursor(5, 20);
oled.print(tr);
oled.setCursor(5, 36);
oled.print(en);
oled.display();
}
void drawError(int code) {
oled.clearDisplay();
oled.setTextSize(2);
oled.setCursor(28, 10);
oled.print("HATA!");
oled.setTextSize(1);
oled.setCursor(28, 40);
oled.print("HTTP ");
oled.print(code);
oled.display();
}
void drawMain() {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
// Ust: ikon + sehir (Y=0-15)
drawWeatherIcon(0, 0, w.descEn);
oled.setTextSize(1);
oled.setCursor(22, 4);
oled.print(w.city);
// Cizgi (Y=17)
oled.drawLine(0, 17, 127, 17, SSD1306_WHITE);
// Merkez: BUYUK sicaklik (Y=20-43)
oled.setTextSize(3);
String t = String(w.tempC);
int tw = t.length() * 18;
oled.setCursor((128 - tw - 14) / 2, 20);
oled.print(t);
oled.setTextSize(2);
oled.setCursor((128 - tw - 14) / 2 + tw, 22);
oled.print((char)247);
oled.setCursor((128 - tw - 14) / 2 + tw + 12, 24);
oled.print("C");
// TR aciklama (ortali, Y=45)
oled.setTextSize(1);
int dw = w.descTr.length() * 6;
oled.setCursor((128 - dw) / 2, 45);
oled.print(w.descTr);
// Alt cizgi (Y=54)
oled.drawLine(0, 54, 127, 54, SSD1306_WHITE);
// Alt: Nem + Ruzgar (Y=56-63 — tam sigar)
oled.setCursor(0, 56);
oled.print("Nem:%");
oled.print(w.humidity);
oled.setCursor(64, 56);
oled.print("R:");
oled.print(w.windKmh);
oled.print("km/h");
oled.display();
}
// ============ API ============
bool fetchWeather() {
if (!wifiOk) return false;
drawStatus("API cagriliyor", "Fetching data...");
HTTPClient http;
String url = String("http://wttr.in/") + CITY + "?format=j1";
http.setTimeout(10000);
http.begin(url);
http.addHeader("User-Agent", "BlueGrays-ESP32/1.0");
int code = http.GET();
bool ok = false;
if (code == 200) {
String body = http.getString();
JsonDocument doc;
DeserializationError err = deserializeJson(doc, body);
if (!err) {
JsonObject cur = doc["current_condition"][0];
w.city = String(CITY);
w.tempC = cur["temp_C"].as<int>();
w.humidity = cur["humidity"].as<int>();
w.descEn = cur["weatherDesc"][0]["value"].as<String>();
w.descTr = translateWeather(w.descEn);
w.windKmh = cur["windspeedKmph"].as<int>();
w.lastFetchMs = millis();
w.valid = true;
ok = true;
}
}
http.end();
if (ok) {
drawStatus("Veri alindi!", "Data received!");
delay(1500);
} else {
drawError(code);
delay(2500);
}
return ok;
}
void setup() {
Wire.begin(21, 22);
if (!oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
while (1);
}
drawSplash();
delay(3000);
drawStatus("WiFi baglaniyor", "Connecting WiFi");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
unsigned long startWifi = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startWifi < 20000) {
delay(400);
}
if (WiFi.status() == WL_CONNECTED) {
wifiOk = true;
drawStatus("WiFi baglandi", "WiFi connected");
delay(1500);
fetchWeather();
} else {
drawStatus("WiFi baglanmadi", "WiFi failed");
delay(2500);
}
}
void loop() {
if (wifiOk && millis() - w.lastFetchMs > FETCH_INTERVAL_MS) {
fetchWeather();
}
if (w.valid) {
drawMain();
delay(3000);
} else {
drawStatus("Veri yok", "No data yet");
delay(3000);
}
}