#include #include #include #include // ───── Konfiguracja WiFi i API ─────────────────────────────────── const char* ssid = "TU WPISZ NAZWĘ HOTSPOT WIFI"; const char* password = "TU WPISZ HASŁO DO WIFI"; const char* apiKey = "MIEJSCE NA TWÓJ KOD API ZE STRONY OPENWEATHERMAP"; const char* url = "https://api.openweathermap.org/data/2.5/weather?q=Warsaw,PL&units=metric&lang=en&appid="; // ─── ADRES IP TWOJEGO ARDUINO UNO ─── const char* urlArduino = "http://MIEJSCE NA ADRES URL PŁYTKI ARDUINO//"; // ───── Mapowanie pinow HUB75 ────────────────────────────────────── #define P_LAT 4 #define P_OE 2 #define P_A 13 #define P_B 21 #define P_C 17 #define P_D 12 #define P_E -1 #define P_CLK 16 #define P_R1 26 #define P_G1 18 #define P_B1 19 #define P_R2 23 #define P_G2 14 #define P_B2 5 #define PANEL_WIDTH 64 #define PANEL_HEIGHT 32 #define PANELS_NUMBER 2 MatrixPanel_I2S_DMA *dma_display = nullptr; HUB75_I2S_CFG *matrix_cfg = nullptr; // ───── Zmienne pogodowe ────────────────────────────────────────── float tempInternet = 0.0; int cisnienie = 0; int wilgInternet = 0; String pogodaOpis = "Brak"; // Zmienne odebrane przez Wi-Fi z Arduino Uno float tempLokalna = 0.0; float wilgLokalna = 0.0; unsigned long lastWeatherUpdate = 0; const unsigned long weatherInterval = 10 * 60 * 1000; // 10 minut unsigned long lastScreenChange = 0; const unsigned long screenInterval = 4000; int currentScreen = 0; void pobierzWszystkieDane(); void konfigurujEkran(); void wyswietlEkran(int stan); void wyswietlWPoziomie(String tekst, int y, uint16_t kolor, int rozmiar, int startX, int szerokoscSektora); void rysujDomek(int x, int y, uint16_t kolor); void rysujDrzewko(int x, int y, uint16_t kolor); void rysujPoziomyTermometr(float temp); void rysujNaczynieZWilgotnoscia(int wilg); void rysujIkonePogody(String stanPogody); void rysujTlokCisnienia(int cisn); // ───── Setup ───────────────────────────────────────────────────── void setup() { Serial.begin(115200); // Pobieramy dane z internetu oraz z Arduino za jednym zamachem pobierzWszystkieDane(); // Odpalamy ekran dopiero po uspieniu Wi-Fi konfigurujEkran(); wyswietlEkran(currentScreen); } // ───── Loop ────────────────────────────────────────────────────── void loop() { unsigned long currentMillis = millis(); // Co 10 minut pobieramy swieze dane if (currentMillis - lastWeatherUpdate >= weatherInterval) { if (dma_display) { dma_display->fillScreen(0); dma_display->flipDMABuffer(); delay(100); } pobierzWszystkieDane(); lastWeatherUpdate = currentMillis; } // Przelaczanie ekranon (0 do 5) if (currentMillis - lastScreenChange >= screenInterval) { currentScreen++; if (currentScreen > 5) { currentScreen = 0; } wyswietlEkran(currentScreen); lastScreenChange = currentMillis; } } // ───── Pobieranie danych z Internetu + Arduino przez Wi-Fi ───── void pobierzWszystkieDane() { Serial.println("Uruchamianie modulu Wi-Fi..."); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); int proby = 0; while (WiFi.status() != WL_CONNECTED && proby < 30) { delay(500); Serial.print("."); proby++; } if (WiFi.status() == WL_CONNECTED) { Serial.println("\nPolaczono z siecia!"); // --- KROK A: OpenWeather --- HTTPClient httpInternet; String pelnyUrl = String(url) + apiKey; httpInternet.begin(pelnyUrl); int httpCode = httpInternet.GET(); if (httpCode == 200) { String payload = httpInternet.getString(); JsonDocument doc; DeserializationError error = deserializeJson(doc, payload); if (!error) { tempInternet = doc["main"]["temp"]; cisnienie = doc["main"]["pressure"]; wilgInternet = doc["main"]["humidity"]; String glownaPogoda = doc["weather"][0]["main"]; if (glownaPogoda == "Clear") pogodaOpis = "SLONCE"; else if (glownaPogoda == "Clouds") pogodaOpis = "CHMURY"; else if (glownaPogoda == "Rain" || glownaPogoda == "Drizzle") pogodaOpis = "DESZCZ"; else if (glownaPogoda == "Snow") pogodaOpis = "SNIEG"; else pogodaOpis = glownaPogoda; Serial.println("Dane z OpenWeather pobrane pomyslnie."); } } httpInternet.end(); delay(1000); // --- KROK B: Arduino Uno --- HTTPClient httpArduino; httpArduino.begin(urlArduino); httpArduino.setTimeout(3000); int httpCodeArduino = httpArduino.GET(); if (httpCodeArduino == 200) { String payloadArduino = httpArduino.getString(); JsonDocument docArduino; DeserializationError errorArduino = deserializeJson(docArduino, payloadArduino); if (!errorArduino) { tempLokalna = docArduino["temp"]; wilgLokalna = docArduino["wilg"]; Serial.println("Dane z Arduino Uno sparsowane pomyslnie."); } } httpArduino.end(); } //pogodaOpis = "SNIEG"; // <--- DOPISZ TĘ LINIJKĘ (Do wyboru: "SLONCE", "CHMURY", "DESZCZ", "SNIEG") WiFi.disconnect(true); WiFi.mode(WIFI_OFF); Serial.println("Wi-Fi uspione. Ekran bezpieczny."); delay(200); } // ───── Konfiguracja sterownika DMA ──────────────────────────────── void konfigurujEkran() { if (dma_display == nullptr) { matrix_cfg = new HUB75_I2S_CFG(PANEL_WIDTH, PANEL_HEIGHT, PANELS_NUMBER); matrix_cfg->gpio.r1 = P_R1; matrix_cfg->gpio.g1 = P_G1; matrix_cfg->gpio.b1 = P_B1; matrix_cfg->gpio.r2 = P_R2; matrix_cfg->gpio.g2 = P_G2; matrix_cfg->gpio.b2 = P_B2; matrix_cfg->gpio.lat = P_LAT; matrix_cfg->gpio.oe = P_OE; matrix_cfg->gpio.clk = P_CLK; matrix_cfg->gpio.a = P_A; matrix_cfg->gpio.b = P_B; matrix_cfg->gpio.c = P_C; matrix_cfg->gpio.d = P_D; matrix_cfg->gpio.e = P_E; matrix_cfg->double_buff = true; matrix_cfg->clkphase = true; matrix_cfg->latch_blanking = 8; dma_display = new MatrixPanel_I2S_DMA(*matrix_cfg); dma_display->begin(); dma_display->setBrightness8(20); } } // ───── Rysowanie na ekranie (Wszystko z ikona: Domek lub Drzewko) ─── void wyswietlEkran(int stan) { if (!dma_display) return; dma_display->fillScreen(0); // ─── LEWY TELEBIM: Zarzadzanie wyswietlaniem animacji i ikon ─── if (stan == 0) { rysujPoziomyTermometr(tempLokalna); } else if (stan == 1) { rysujNaczynieZWilgotnoscia((int)wilgLokalna); } else if (stan == 2) { rysujPoziomyTermometr(tempInternet); } else if (stan == 3) { rysujNaczynieZWilgotnoscia(wilgInternet); } else if (stan == 4) { // NAPRAWIONE: Teraz poprawnie wywolujemy rysowanie tloka w sekcji IF-ELSE rysujTlokCisnienia(cisnienie); } else if (stan == 5) { rysujIkonePogody(pogodaOpis); } // ─── PRAWY TELEBIM: Dane i opisy tekstowe ─── String etykieta = ""; String wartosc = ""; uint16_t kolorEtykiety = dma_display->color565(120, 120, 120); uint16_t kolorWartosci = 0; int typIkony = 0; switch(stan) { case 0: etykieta = "TEMP."; wartosc = String(tempLokalna, 1) + "\367C"; kolorWartosci = dma_display->color565(255, 130, 0); typIkony = 2; break; case 1: etykieta = "WILG."; wartosc = String((int)wilgLokalna) + "%"; kolorWartosci = dma_display->color565(0, 255, 255); typIkony = 2; break; case 2: etykieta = "TEMP."; wartosc = String(tempInternet, 1) + "\367C"; kolorWartosci = dma_display->color565(255, 50, 50); typIkony = 1; break; case 3: etykieta = "WILG."; wartosc = String(wilgInternet) + "%"; kolorWartosci = dma_display->color565(50, 150, 255); typIkony = 1; break; case 4: etykieta = "CISN."; wartosc = String(cisnienie) + "hPa"; kolorWartosci = dma_display->color565(50, 255, 50); typIkony = 1; break; case 5: etykieta = "WARUNKI"; if (pogodaOpis == "Slonecznie" || pogodaOpis == "SLONCE") wartosc = "SLONCE"; else if (pogodaOpis == "Chmury" || pogodaOpis == "CHMURY") wartosc = "CHMURY"; else if (pogodaOpis == "Deszcz" || pogodaOpis == "DESZCZ") wartosc = "DESZCZ"; else if (pogodaOpis == "Snieg" || pogodaOpis == "SNIEG") wartosc = "SNIEG"; else wartosc = pogodaOpis.substring(0, 5); kolorWartosci = dma_display->color565(255, 255, 255); typIkony = 1; break; } if (typIkony == 1) { rysujDrzewko(66, 6, dma_display->color565(34, 139, 34)); } else if (typIkony == 2) { rysujDomek(66, 8, dma_display->color565(100, 149, 237)); } wyswietlWPoziomie(etykieta, 1, kolorEtykiety, 1, 82, 46); int rozmiarWartosci = (wartosc.length() > 4 || stan == 0) ? 1 : 2; int yWartosci = (rozmiarWartosci == 2) ? 17 : 21; wyswietlWPoziomie(wartosc, yWartosci, kolorWartosci, rozmiarWartosci, 82, 46); dma_display->flipDMABuffer(); } // ───── Centrowanie tekstu w wyznaczonym sektorze ────────────────── void wyswietlWPoziomie(String tekst, int y, uint16_t kolor, int rozmiar, int startX, int szerokoscSektora) { dma_display->setTextSize(rozmiar); int szerokoscZnaku = 6 * rozmiar; int calkowitaSzerokosc = tekst.length() * szerokoscZnaku; if (tekst.indexOf("\367") != -1) { calkowitaSzerokosc -= szerokoscZnaku; } int x = startX + ((szerokoscSektora - calkowitaSzerokosc) / 2); if (x < startX) x = startX; dma_display->setTextColor(kolor); dma_display->setCursor(x, y); dma_display->print(tekst); } // ───── Funkcja rysujaca symboliczny domek ───────────────────────── void rysujDomek(int x, int y, uint16_t kolor) { dma_display->drawTriangle(x + 6, y, x, y + 6, x + 12, y + 6, kolor); dma_display->drawRect(x + 1, y + 7, 11, 10, kolor); dma_display->fillRect(x + 5, y + 11, 3, 6, kolor); } // ───── Funkcja rysujaca symboliczne drzewko ─────────────────────── void rysujDrzewko(int x, int y, uint16_t kolor) { dma_display->fillTriangle(x + 6, y, x + 2, y + 5, x + 10, y + 5, kolor); dma_display->fillTriangle(x + 6, y + 3, x + 1, y + 9, x + 11, y + 9, kolor); dma_display->fillTriangle(x + 6, y + 6, x, y + 13, x + 12, y + 13, kolor); uint16_t kolorPnia = dma_display->color565(139, 69, 19); dma_display->fillRect(x + 5, y + 14, 3, 5, kolorPnia); } // ───── LEWY PANEL: Rysowanie poziomego termometru ────────────────── void rysujPoziomyTermometr(float temp) { if (temp < -15.0) temp = -15.0; if (temp > 35.0) temp = 35.0; int xTermometru = 6; int yTermometru = 13; int maxSzerokoscSlupka = 46; int szerokoscPaska = (int)((temp + 15.0) * (maxSzerokoscSlupka / 50.0)); uint8_t r = 0, g = 0, b = 0; if (temp <= 0.0) { b = 255; g = map((int)temp, -15, 0, 50, 200); } else if (temp > 0.0 && temp <= 20.0) { g = 255; r = map((int)temp, 0, 20, 0, 255); } else { r = 255; g = map((int)temp, 20, 35, 255, 0); } uint16_t kolorSlupka = dma_display->color565(r, g, b); uint16_t kolorRamki = dma_display->color565(80, 80, 80); dma_display->fillCircle(xTermometru + 2, yTermometru + 2, 4, kolorSlupka); dma_display->drawCircle(xTermometru + 2, yTermometru + 2, 5, kolorRamki); dma_display->drawRect(xTermometru + 7, yTermometru, maxSzerokoscSlupka + 2, 6, kolorRamki); if (szerokoscPaska > 0) { dma_display->fillRect(xTermometru + 8, yTermometru + 2, szerokoscPaska, 2, kolorSlupka); } } // ───── LEWY PANEL: Rysowanie napelniajacego sie naczynia woda ────── void rysujNaczynieZWilgotnoscia(int wilg) { if (wilg < 0) wilg = 0; if (wilg > 100) wilg = 100; int xNaczynia = 24; int yNaczynia = 7; int wysNaczynia = 18; int szerNaczynia = 16; uint16_t kolorSzkla = dma_display->color565(100, 100, 100); uint16_t kolorWody = dma_display->color565(0, 120, 255); dma_display->drawFastVLine(xNaczynia, yNaczynia, wysNaczynia, kolorSzkla); dma_display->drawFastVLine(xNaczynia + szerNaczynia - 1, yNaczynia, wysNaczynia, kolorSzkla); dma_display->drawFastHLine(xNaczynia, yNaczynia + wysNaczynia - 1, szerNaczynia, kolorSzkla); int maxWysWody = wysNaczynia - 3; int pikseleWody = map(wilg, 0, 100, 0, maxWysWody); if (pikseleWody > 0) { int xSrodka = xNaczynia + 1; int szerSrodka = szerNaczynia - 2; int yDnaWody = yNaczynia + wysNaczynia - 2; dma_display->fillRect(xSrodka, yDnaWody - pikseleWody + 1, szerSrodka, pikseleWody, kolorWody); } } // ───── LEWY PANEL: Rysowanie poziomego tloka cisnienia ────────────── void rysujTlokCisnienia(int cisn) { if (cisn < 950) cisn = 950; if (cisn > 1050) cisn = 1050; int xCylindra = 4; int yCylindra = 8; int szerCylindra = 54; int wysCylindra = 16; uint16_t kolorMetal = dma_display->color565(130, 130, 130); uint16_t kolorWalu = dma_display->color565(180, 140, 40); uint16_t kolorGlowicy = dma_display->color565(220, 220, 220); int xTlokCzolo = map(cisn, 950, 1050, 12, 44); uint8_t intensywnosc = map(cisn, 950, 1050, 40, 255); uint16_t kolorKomory = dma_display->color565(intensywnosc, 50, 255 - intensywnosc); int startKomoryX = xTlokCzolo + 4; int szerKomory = (xCylindra + szerCylindra - 1) - startKomoryX; if (szerKomory > 0) { dma_display->fillRect(startKomoryX, yCylindra + 2, szerKomory, wysCylindra - 4, kolorKomory); } dma_display->drawFastHLine(xCylindra, yCylindra, szerCylindra, kolorMetal); dma_display->drawFastHLine(xCylindra, yCylindra + wysCylindra - 1, szerCylindra, kolorMetal); dma_display->drawFastVLine(xCylindra + szerCylindra - 1, yCylindra, wysCylindra, kolorMetal); dma_display->fillRect(xCylindra, yCylindra + (wysCylindra / 2) - 1, xTlokCzolo - xCylindra, 3, kolorWalu); dma_display->fillRect(xTlokCzolo, yCylindra + 1, 4, wysCylindra - 2, kolorGlowicy); dma_display->drawFastVLine(xTlokCzolo + 1, yCylindra + 1, wysCylindra - 2, dma_display->color565(30, 30, 30)); dma_display->drawFastVLine(xTlokCzolo + 2, yCylindra + 1, wysCylindra - 2, dma_display->color565(30, 30, 30)); } // ───── LEWY PANEL: Rysowanie duzych ikon pogodowych (32x32) ──────── void rysujIkonePogody(String stanPogody) { int srodekX = 32; uint16_t zolty = dma_display->color565(255, 215, 0); uint16_t bialy = dma_display->color565(255, 255, 255); uint16_t szary = dma_display->color565(150, 160, 170); uint16_t szaryCiemny = dma_display->color565(90, 100, 110); uint16_t niebieski = dma_display->color565(0, 150, 255); if (stanPogody == "SLONCE") { // Tarcza slonca (srodekX = 32, Y = 15, promien = 6) dma_display->fillCircle(srodekX, 15, 6, zolty); // Promienie pionowe i poziome (oddalone o 2 piksele od krawedzi tarczy) dma_display->drawFastVLine(srodekX, 5, 2, zolty); // Gorny promien dma_display->drawFastVLine(srodekX, 23, 2, zolty); // Dolny promien dma_display->drawFastHLine(srodekX - 10, 15, 2, zolty); // Lewy promien dma_display->drawFastHLine(srodekX + 8, 15, 2, zolty); // Prawy promien // Promienie ukosne (rowniez odsunięte od tarczy) dma_display->drawLine(srodekX - 8, 9, srodekX - 6, 7, zolty); // Gorny lewy dma_display->drawLine(srodekX + 8, 9, srodekX + 6, 7, zolty); // Gorny prawy dma_display->drawLine(srodekX - 8, 21, srodekX - 6, 23, zolty); // Dolny lewy dma_display->drawLine(srodekX + 8, 21, srodekX + 6, 23, zolty); // Dolny prawy } else if (stanPogody == "CHMURY" || stanPogody == "DESZCZ" || stanPogody == "SNIEG") { // 1. CHMURA TYLNA LEWA int tlX = srodekX - 11; int tlY = 10; dma_display->fillCircle(tlX, tlY - 1, 4, szaryCiemny); dma_display->fillCircle(tlX - 4, tlY + 2, 3, szaryCiemny); dma_display->fillCircle(tlX + 4, tlY + 2, 3, szaryCiemny); dma_display->fillCircle(tlX, tlY + 3, 3, szaryCiemny); // 2. CHMURA TYLNA PRAWA int tpX = srodekX + 13; int tpY = 14; dma_display->fillCircle(tpX, tpY - 1, 3, szaryCiemny); dma_display->fillCircle(tpX - 3, tpY + 2, 2, szaryCiemny); dma_display->fillCircle(tpX + 3, tpY + 2, 2, szaryCiemny); dma_display->fillCircle(tpX, tpY + 2, 2, szaryCiemny); // 3. CHMURA GLOWNA PRZEDNIA int yC = 13; dma_display->fillCircle(srodekX, yC - 2, 7, szary); dma_display->fillCircle(srodekX - 4, yC - 1, 6, szary); dma_display->fillCircle(srodekX + 4, yC - 1, 5, szary); dma_display->fillCircle(srodekX - 8, yC + 3, 5, szary); dma_display->fillCircle(srodekX + 8, yC + 3, 4, szary); dma_display->fillCircle(srodekX - 4, yC + 4, 4, szary); dma_display->fillCircle(srodekX + 3, yC + 4, 4, szary); dma_display->fillCircle(srodekX, yC + 4, 4, szary); dma_display->fillRect(srodekX - 6, yC, 13, 5, szary); // --- OPADY (DESZCZ / SNIEG) --- if (stanPogody == "DESZCZ") { dma_display->drawLine(srodekX - 14, tlY + 7, srodekX - 15, tlY + 10, niebieski); dma_display->drawLine(srodekX - 7, yC + 9, srodekX - 8, yC + 12, niebieski); dma_display->drawLine(srodekX - 1, yC + 10, srodekX - 2, yC + 13, niebieski); dma_display->drawLine(srodekX + 5, yC + 9, srodekX + 4, yC + 12, niebieski); dma_display->drawLine(srodekX + 12, tpY + 6, srodekX + 11, tpY + 9, niebieski); } else if (stanPogody == "SNIEG") { dma_display->drawPixel(srodekX - 13, tlY + 8, bialy); dma_display->drawPixel(srodekX - 8, yC + 10, bialy); dma_display->drawPixel(srodekX - 3, yC + 13, bialy); dma_display->drawPixel(srodekX + 1, yC + 11, bialy); dma_display->drawPixel(srodekX + 6, yC + 10, bialy); dma_display->drawPixel(srodekX + 12, tpY + 7, bialy); dma_display->drawPixel(srodekX + 2, yC + 15, bialy); } } else { wyswietlWPoziomie(stanPogody.substring(0, 5), 13, bialy, 1, 0, 64); } }