#include "moon.h"
#include "MoonPhase.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <math.h>
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
// CONFIG
const char* ssid = "Wokwi-GUEST";
const char* password = "";
#define updateHour 1
// Timezone configuration
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 1 * 3600;
const int daylightOffset_sec = 3600;
// LOCATION CONFIG
#define USE_MANUAL_LOCATION true
const char* MANUAL_CITY = "Caino";
const char* MANUAL_COUNTRY = "Italy";
const char* MANUAL_TIMEZONE = "Europe/Rome";
const float MANUAL_LAT = 45.613418;
const float MANUAL_LON = 10.312439;
// Moon display parameters
const int CX = 120;
const int CY = 120;
const int CR = 120;
// MoonPhase object
MoonPhase moon;
void showmoon(float phase);
const char* getPhaseName(float phase, float fraction);
void printLocalTime() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
return;
}
char timeString[64];
strftime(timeString, sizeof(timeString), "%A, %B %d %Y %H:%M:%S", &timeinfo);
Serial.printf("🕒 Current Time: %s", timeString);
}
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
displayConnectingMessage();
sprite.setColorDepth(16);
sprite.createSprite(240, 240);
delay(3000);
Serial.println("\n🌙 ESP32 Moon Phase Tracker");
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n✅ Connected!");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.print("Waiting for time synchronization");
struct tm timeinfo;
int retry = 0;
while (!getLocalTime(&timeinfo) && retry < 10) {
delay(1000);
Serial.print(".");
retry++;
}
if (retry >= 10) {
Serial.println("\n❌ Time synchronization failed!");
} else {
Serial.println("\n✅ Time synchronized!");
printLocalTime();
}
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
if (USE_MANUAL_LOCATION) {
Serial.println("\n📍 Manual Location: Caino, Italy");
Serial.printf("🌐 Timezone: %s\n", MANUAL_TIMEZONE);
Serial.printf("📡 Coordinates: %.6f, %.6f\n", MANUAL_LAT, MANUAL_LON);
processMoonPhase();
} else {
HTTPClient http;
http.begin("http://ip-api.com/json/");
int httpCode = http.GET();
if (httpCode == 200) {
String payload = http.getString();
StaticJsonDocument<512> doc;
deserializeJson(doc, payload);
String city = doc["city"];
String country = doc["country"];
String timezone = doc["timezone"];
float lat = doc["lat"];
float lon = doc["lon"];
Serial.printf("\n📍 Detected Location: %s, %s\n", city.c_str(), country.c_str());
Serial.printf("🌐 Timezone: %s\n", timezone.c_str());
Serial.printf("📡 Coordinates: %.4f, %.4f\n", lat, lon);
processMoonPhase();
} else {
Serial.printf("❌ HTTP Error: %d\n", httpCode);
Serial.println("⚠️ Fallback to manual location");
processMoonPhase();
}
http.end();
}
} else {
Serial.println("❌ WiFi disconnected!");
WiFi.begin(ssid, password);
}
Serial.println("\n⏰ Next update in 1 hour...");
delay(updateHour * 60 * 60 * 1000);
}
void processMoonPhase() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("❌ Failed to obtain time");
return;
}
// Library calculation at current time
time_t t = time(NULL);
moon.calculate(t);
// Draw (u Ciebie showmoon oczekuje phase 0..1: 0=new, 0.5=full)
showmoon((float)moon.phase);
// Kierunek cyklu
bool waxing = (moon.phase < 0.5f); // true = przybywa
const char* arrow = waxing ? "⬆️" : "⬇️";
const char* dir = waxing ? "przybywa" : "ubywa";
// RAW DATA FROM LIBRARY
Serial.println("====== MOONPHASE (library) ======");
Serial.printf("🗓️ Data: %04d-%02d-%02d\n", timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday);
Serial.printf("🌙 Faza (0..1): %.6f %s %s\n", moon.phase, arrow, dir); // 0 - 1, 0.5 = full
Serial.printf("🌙 Nazwa fazy Księżyca: %s\n", moon.phaseName);
Serial.printf("🎂 Wiek w dniach bieżącego cyklu: %.4f\n", moon.age);
Serial.printf("🌓 Ułamek oświetlonego dysku (0..1): %.6f\n", moon.fraction);
Serial.printf("🌕 Oświetlenie: %.1f%%\n", moon.fraction * 100.0);
Serial.printf("🔍 Odległość Księżyca w promieniach Ziemi: %.3f\n", moon.distance); // Earth radii (as in lib)
Serial.printf("🔍 Szerokość ekliptyczna Księżyca: %.3f°\n", moon.latitude);
Serial.printf("🔍 Długość ekliptyczna Księżyca: %.3f°\n", moon.longitude);
Serial.printf("♋ Nazwa konstelacji zodiaku: %s\n", moon.zodiacName);
}
void showmoon(float /*unused*/) {
const float EDGE = 0.985f;
const float TILT_DEG = 0.0f;
const float TILT = TILT_DEG * (M_PI / 180.0f);
const int MIRROR = 1;
float k = (float)moon.fraction; // 0..1
bool waxing = (moon.phase < 0.5f); // ⬆️/⬇️
tft.setSwapBytes(true);
tft.pushImage(0, 0, 240, 240, moonallArray[0]);
float d = EDGE * (2.0f * CR * k);
float dx = d * cosf(TILT);
float dy = d * sinf(TILT);
int cx = (int)lroundf(CX + MIRROR * (waxing ? -dx : +dx));
int cy = (int)lroundf(CY + MIRROR * (waxing ? +dy : -dy));
cx = constrain(cx, -CR, 240 + CR);
cy = constrain(cy, -CR, 240 + CR);
const int R_FULL = 120;
const int R_RING = 118;
const float D_EPS = 1.5f;
int Rshadow = (d < D_EPS) ? R_FULL : R_RING;
tft.fillCircle(cx, cy, Rshadow, TFT_BLACK);
bool brightRight = waxing ^ (MIRROR == -1);
Serial.printf("🌘 k=%.4f (%.1f%%) | phase=%.6f %s | d=%.1f | Shadow: X=%d Y=%d R=%d | Bright: %s\n",
k, k*100.0f, moon.phase, waxing ? "⬆️" : "⬇️",
d, cx, cy, Rshadow, brightRight ? "D (right)" : "C (left)");
}
/*
void showmoon(float ) {
const float PHASE_SCALE = 60.0f; // Amplituda ruchu cienia
float phase = moon.phase; // 0.0 - 1.0 (0=now, 0.5=pełnia)
float fraction = moon.fraction;
tft.setSwapBytes(true);
tft.pushImage(0, 0, 240, 240, moonallArray[0]);
// Formuła: 120 + 60 * sin(2π * phase)
float rawCX = CX + PHASE_SCALE * sinf(2 * M_PI * phase);
// Ogranicz do ekranu (0-240)
int cx = constrain((int)rawCX, 0, 240);
// --- OBSŁUGA PEŁNI POPRZEZ OGRANICZENIE ---
// Dla phase=0.5 → cx=120±60=60 lub 180 → ograniczone do 0-240
// W przypadku pełni (phase≈0.5) cieniu będzie poza ekranem
Serial.printf("🌘: phase=%.3f → rawCX=%.1f → ograniczono do CX=%d\n", phase, rawCX, cx);
Serial.printf("🌘: fraction=%.6f | status: %s\n", fraction, (fraction <= 0.01f) ? "🌑 NOW" : (fraction >= 0.99f) ? "🌕 PEŁNIA" : "🌘 FAZA");
// --- RYSUJ CIEŃ TYLKO GDY WIDOCZNY ---
//if (fraction > 0.01f && fraction < 0.99f) {
tft.fillCircle(cx, CY, CR, TFT_BLACK);
//}
}*/
const char* getPhaseName(float phase, float fraction) {
// This function is now redundant as MoonPhase library provides phaseName
return moon.phaseName;
}
void displayConnectingMessage() {
tft.setSwapBytes(false);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Connecting", 120, 120);
}