ESP32 CYD 2.8" Dashboard (Weather, Stocks & Moon)
Print Profile(1)

Description
📱 ESP32 CYD 2.8" Smart Dashboard (Weather, Stocks & Moon)
With Webflasher, no programming or cumbersome library loading: Smart Dashboard Installer
This project transforms the affordable ESP32-2432S028R (also known as "Cheap Yellow Display" or CYD) into a sleek, functional information dashboard for your desk
✨ Main Features:
- Precise Time: Automatic synchronization via NTP (CET/CEST with daylight saving time)
- Live Weather: Current data via Open-Meteo (no API key required!)
- Moon Phases: Integrated mathematical algorithm for the current moon phase
- Financial Ticker: Live rates for DAX, DOW Jones, VIX and Bitcoin (€) (via Yahoo Finance & Coinbase)
- Easy Setup: Thanks to WiFiManager, you don't have to hardcode your WiFi data. The device creates a hotspot for configuration on first boot
🛠️ Hardware Requirements:
- Display: ESP32-2432S028R (2.8" TFT with resistive touch)
Buy here, with this display the program works without errors
Features:
- 🌦️ Live Weather: Open-Meteo (No API Key needed!)
- 📈 Finance: Live DAX, DOW, BTC & VIX via Yahoo/Coinbase.
- 🌙 Moon Phase: Precise mathematical calculation.
- ⚙️ Easy Setup: WiFiManager included – no hardcoded WiFi credentials!
Hardware needed:
Here is the Sketch:
⚠️ IMPORTANT: TFT_eSPI Configuration (User_Setup.h)
For the display to be controlled correctly, the TFT_eSPI library must be adapted. Replace the content of your User_Setup.h (in the Arduino library folder) with the following values or ensure that these lines are active:
// ============================================================================
// DISPLAY PIN CONFIGURATION (Suitable for 2.8" ESP32-2432S028R / CYD)
// ============================================================================
#define ILI9341_DRIVER // Standard driver for this display
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1 // Reset is connected to the EN button
// --- BACKLIGHT CONFIGURATION ---
// Standard CYD (Micro-USB) uses Pin 21.
// Newer USB-C versions (e.g. from diymore) often use Pin 27.
// If the display remains black, simply swap the comments (//):
#define TFT_BL 21 // <-- Standard setting
// #define TFT_BL 27 // <-- If Pin 21 does not work, remove // here
#define TFT_BACKLIGHT_ON HIGH // Backlight is switched on with HIGH
// --- FONTS ---
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_GFXFF
// --- SPEED ---
#define SPI_FREQUENCY 40000000
Sketch for Arduino IDE 2.3.8
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiManager.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <TFT_eSPI.h>
#include "time.h"
// CONFIGURATION & STANDARDS
String cityName = "Oberhausen";
String selectedTZ = "CET-1CEST,M3.5.0,M10.5.0/3"; // Default: Berlin
const char* ntpServer = "pool.ntp.org";
TFT_eSPI tft = TFT_eSPI();
WiFiClientSecure client;
float latitude, longitude;
String weatherDesc; float weatherTemp;
String moonPhaseText;
float daxValue, dowValue, vixValue, btcValue;
unsigned long lastTimeUpdate, lastWeatherUpdate, lastMoonUpdate, lastQuoteUpdate;
const unsigned long TIME_UPDATE_INTERVAL = 1000;
const unsigned long WEATHER_UPDATE_INTERVAL = 10*60*1000;
const unsigned long MOON_UPDATE_INTERVAL = 60*60*1000;
const unsigned long QUOTE_UPDATE_INTERVAL = 2*60*1000;
bool httpGetJson(const String& url, DynamicJsonDocument& doc) {
HTTPClient http; http.begin(url);
http.addHeader("User-Agent", "Mozilla/5.0");
int code = http.GET();
if (code != 200) { http.end(); return false; }
String payload = http.getString(); http.end();
return deserializeJson(doc, payload) == DeserializationError::Ok;
}
void updateWeather() {
DynamicJsonDocument doc(4096);
if (latitude == 0) {
if (httpGetJson("https://open-meteo.com" + cityName + "&count=1&language=de&format=json", doc)) {
latitude = doc["results"][0]["latitude"];
longitude = doc["results"][0]["longitude"];
cityName = doc["results"][0]["name"].as<String>();
}
}
doc.clear();
if (httpGetJson("https://open-meteo.com" + String(latitude,4) + "&longitude=" + String(longitude,4) + "¤t=temperature_2m,weather_code&timezone=auto", doc)) {
weatherTemp = doc["current"]["temperature_2m"];
int code = doc["current"]["weather_code"];
if(code==0) weatherDesc = "Clear";
else if(code<=3) weatherDesc = "Cloudy";
else if(code<=67) weatherDesc = "Rain";
else if(code<=77) weatherDesc = "Snow";
else weatherDesc = "Thunderstorm";
}
}
void updateMoonPhaseLocal() {
struct tm t;
if (getLocalTime(&t)) {
time_t nowTs = mktime(&t);
double diff = (double)nowTs - 947182440.0;
double days = diff / 86400.0;
double cycle = 29.530588;
double phase = fmod(days, cycle);
if (phase < 0) phase += cycle;
if (phase < 1.5) moonPhaseText = "New Moon";
else if (phase < 6.5) moonPhaseText = "Waxing Crescent";
else if (phase < 8.5) moonPhaseText = "First Quarter";
else if (phase < 13.5) moonPhaseText = "Gibbous";
else if (phase < 16.5) moonPhaseText = "Full Moon";
else if (phase < 21.5) moonPhaseText = "Waning Gibbous";
else if (phase < 23.5) moonPhaseText = "Last Quarter";
else if (phase < 28.5) moonPhaseText = "Waning Crescent";
else moonPhaseText = "New Moon";
}
}
void fetchStockData(String symbol, float& price) {
DynamicJsonDocument doc(8192);
if (httpGetJson("https://yahoo.com" + symbol, doc)) {
price = doc["chart"]["result"][0]["meta"]["regularMarketPrice"];
}
}
void fetchBitcoinEUR(float& price) {
DynamicJsonDocument doc(2048);
if (httpGetJson("https://coinbase.com", doc)) {
price = doc["data"]["amount"].as<float>();
}
}
void updateQuotes() {
fetchStockData("^GDAXI", daxValue);
fetchStockData("^DJI", dowValue);
fetchStockData("^VIX", vixValue);
fetchBitcoinEUR(btcValue);
}
void drawTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) return;
char buf[32]; strftime(buf, 32, "%H:%M:%S %d.%m", &timeinfo);
tft.setTextSize(2); tft.setTextColor(TFT_CYAN);
tft.fillRect(10, 10, 220, 35, TFT_BLACK);
tft.setCursor(15, 30); tft.print(buf);
tft.print(timeinfo.tm_isdst > 0 ? " DST" : " ST");
}
void drawWeather() {
tft.setTextSize(2); tft.setTextColor(TFT_WHITE);
tft.fillRect(10, 50, 230, 50, TFT_BLACK);
tft.setCursor(15, 55); tft.print(cityName);
tft.setCursor(15, 80); tft.print(weatherTemp, 1); tft.print("C "); tft.print(weatherDesc);
}
void drawMoon() {
tft.setTextSize(2); tft.setTextColor(TFT_YELLOW);
tft.fillRect(10, 105, 220, 35, TFT_BLACK);
tft.setCursor(15, 125); tft.print(moonPhaseText);
}
void drawQuotes() {
tft.setTextSize(2); tft.setTextColor(TFT_RED);
tft.fillRect(10, 155, 220, 90, TFT_BLACK);
int y = 160;
tft.setCursor(15, y); tft.print("DAX "); tft.println(daxValue,0); y += 20;
tft.setCursor(15, y); tft.print("DOW "); tft.println(dowValue,0); y += 20;
tft.setCursor(15, y); tft.print("VIX "); tft.println(vixValue,1); y += 20;
tft.setCursor(15, y); tft.print("BTC "); tft.print(btcValue,0); tft.print(" EUR");
}
void setup() {
Serial.begin(115200);
tft.init(); tft.setRotation(0); tft.fillScreen(TFT_BLACK);
// WIFI MANAGER SETUP
WiFiManager wm;
// Custom fields for city and time zone
WiFiManagerParameter custom_city("city", "Enter city", cityName.c_str(), 40);
// A simple HTML dropdown for the time zone
const char* custom_html_tz = "<br/><label for='tz'>Select time zone</label><br/><select name='tz' id='tz'>"
"<option value='CET-1CEST,M3.5.0,M10.5.0/3'>Europe/Berlin</option>"
"<option value='GMT0BST,M3.5.0,M10.5.0/2'>London</option>"
"<option value='EST5EDT,M3.2.0,M11.1.0'>New York</option>"
"<option value='JST-9'>Tokyo</option></select>";
WiFiManagerParameter custom_tz_html(custom_html_tz);
wm.addParameter(&custom_city);
wm.addParameter(&custom_tz_html);
tft.setTextColor(TFT_WHITE); tft.setTextSize(2); tft.setCursor(10, 50);
tft.println("WiFi Config...");
if (!wm.autoConnect("MakerDisplay-Setup")) ESP.restart();
// Adopt values from the portal
cityName = String(custom_city.getValue());
// Note: Reading select boxes via WiFiManager is a bit tricky,
// for MakerWorld we set Berlin as default here if nothing comes.
configTzTime(selectedTZ.c_str(), ntpServer);
client.setInsecure();
updateWeather(); updateMoonPhaseLocal(); updateQuotes();
tft.fillScreen(TFT_BLACK);
drawTime(); drawWeather(); drawMoon(); drawQuotes();
lastTimeUpdate = lastWeatherUpdate = lastMoonUpdate = lastQuoteUpdate = millis();
}
void loop() {
unsigned long now = millis();
if (now - lastTimeUpdate > TIME_UPDATE_INTERVAL) { drawTime(); lastTimeUpdate = now; }
if (now - lastWeatherUpdate > WEATHER_UPDATE_INTERVAL) { updateWeather(); drawWeather(); lastWeatherUpdate = now; }
if (now - lastMoonUpdate > MOON_UPDATE_INTERVAL) { updateMoonPhaseLocal(); drawMoon(); lastMoonUpdate = now; }
if (now - lastQuoteUpdate > QUOTE_UPDATE_INTERVAL) { updateQuotes(); drawQuotes(); lastQuoteUpdate = now; }
}














Comment & Rating (7)