#include "config.h" #include #include #include #include #include #include #include #include #include "bitmaps.h" #include "netman.h" // Init display and wifi Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); netman netman(display); WiFiClient client; // Define NTP Client to get time WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 43200000); // Define these in config.h const String Location = LOCATION; const String API_Key = API_KEY; void capitalizeFirstLetter(char* str) { if (str != nullptr && *str != '\0') { str[0] = toupper(str[0]); } } void beep(int buzz) { tone(PIN_BUZZER, buzz, 100); delay(100); noTone(PIN_BUZZER); } void initSystems() { pinMode(PIN_BTN_L, INPUT); pinMode(PIN_BTN_M, INPUT); pinMode(PIN_BTN_R, INPUT); pinMode(PIN_BUZZER, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); // Initialize the SSD1306 display if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Adjust I2C address if needed for(;;); // Don't proceed, loop forever } // Rotate the display 180 degrees display.setRotation(2); // Clear the display buffer display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); display.setCursor(0, 0); display.display(); } // Timers for dynamic updates unsigned long lastWeatherUpdate = 0; unsigned long lastDisplayOverride = 0; const unsigned long weatherUpdateInterval = 1800000; // 30 minutes in milliseconds const unsigned long displayOverrideTimeout = 60000; // 1 minute in milliseconds bool is_display_off = false; bool display_override = false; void commonButtonHandler() { unsigned long currentMillis = millis(); static unsigned long leftPressStart = 0; static unsigned long middlePressStart = 0; static unsigned long rightPressStart = 0; static bool leftBeeped = false; static bool middleBeeped = false; static bool rightBeeped = false; bool leftPressed = (digitalRead(PIN_BTN_L) == HIGH); bool middlePressed = (digitalRead(PIN_BTN_M) == HIGH); bool rightPressed = (digitalRead(PIN_BTN_R) == HIGH); // Short press detection if (leftPressed) { if ((currentMillis - leftPressStart > 50)) { // Debounce delay if (!leftBeeped) { beep(1000); // Play beep sound leftBeeped = true; // Set beeped state // Handle left short press action here } } } else { leftPressStart = currentMillis; // Reset the timer if button is not pressed leftBeeped = false; // Reset beeped state } if (middlePressed) { if ((currentMillis - middlePressStart > 50)) { // Debounce delay if (!middleBeeped) { beep(1000); // Play beep sound middleBeeped = true; // Set beeped state // Handle middle short press action here } } } else { middlePressStart = currentMillis; // Reset the timer if button is not pressed middleBeeped = false; // Reset beeped state } if (rightPressed) { if ((currentMillis - rightPressStart > 50)) { // Debounce delay if (!rightBeeped) { beep(1000); // Play beep sound rightBeeped = true; // Set beeped state if (is_display_off) { display.ssd1306_command(SSD1306_DISPLAYON); // Turn on display is_display_off = false; display_override = true; beep(1300); } } } } else { rightPressStart = currentMillis; // Reset the timer if button is not pressed rightBeeped = false; // Reset beeped state } // Long press detection if (leftPressed && (currentMillis - leftPressStart > 2000)) { if (!leftBeeped) { beep(1000); // Play beep sound leftBeeped = true; // Set beeped state // Handle left long press action here } } if (middlePressed && (currentMillis - middlePressStart > 2000)) { if (!middleBeeped) { beep(1000); // Play beep sound middleBeeped = true; // Set beeped state // Handle middle long press action here } } if (rightPressed && (currentMillis - rightPressStart > 2000)) { if (!is_display_off) { beep(1300); display.ssd1306_command(SSD1306_DISPLAYOFF); // Turn off display is_display_off = true; display_override = false; beep(1000); } } // Combination of Left and Middle long press if (leftPressed && middlePressed && (currentMillis - leftPressStart > 2000) && (currentMillis - middlePressStart > 2000)) { ESP.restart(); } } void powerSaveCheck() { int currentHour = timeClient.getHours(); if ((currentHour >= 22 || currentHour < 8) && !is_display_off && !display_override) { display.ssd1306_command(SSD1306_DISPLAYOFF); // Turn off display for power-saving is_display_off = true; } } int icon = 0; bool fetchWeatherData() { // Get current time time_t epochTime = timeClient.getEpochTime(); struct tm *ptm = gmtime((time_t *)&epochTime); int currentDay = ptm->tm_mday; int currentMonth = ptm->tm_mon + 1; int currentYear = ptm->tm_year + 1900; HTTPClient http; int attempt; bool dataFetched = false; // Request current weather for (attempt = 0; attempt < 3; attempt++) { // Request current weather http.begin(client, "http://api.openweathermap.org/data/2.5/weather?q=" + Location + "&APPID=" + API_Key); int httpCode = http.GET(); if (httpCode > 0) { String payload = http.getString(); DynamicJsonDocument doc(1024); DeserializationError error = deserializeJson(doc, payload); if (!error) { const char* state = doc["weather"][0]["description"]; capitalizeFirstLetter(const_cast(state)); float temp = doc["main"]["temp"].as() - 273.15; int humidity = doc["main"]["humidity"]; float pressure = doc["main"]["pressure"].as() / 1000; float wind_speed = doc["wind"]["speed"].as(); int statusSize = strlen(state) > 12 ? 1 : 2; // Display data display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.printf("%d-%d-%d\r\n", currentYear, currentMonth, currentDay); display.setCursor(0, 9); display.printf("%s", Location.c_str()); display.setTextSize(statusSize); display.setCursor(0, 21); display.printf("%s\r\n", state); display.setCursor(0, 39); display.setTextSize(1); display.printf(" %5.2f C %d%%\r\n", temp, humidity); display.drawRect(43, 39, 3, 3, WHITE); // Degree symbol display.setCursor(0, 52); display.printf(" %.3fbar %.1fm/s \r\n", pressure, wind_speed); display.drawLine(0, 18, 127, 18, 1); display.drawLine(65, 18, 65, 0, 1); display.drawBitmap(0, 38, temperature_icon, 10, 10, WHITE); display.drawBitmap(74, 38, humidity_icon, 10, 10, WHITE); display.drawBitmap(0, 51, pressure_icon, 10, 10, WHITE); display.drawBitmap(74, 51, wind_icon, 10, 10, WHITE); display.display(); http.end(); dataFetched = true; break; // Exit the loop after successful fetch } } // Display attempt number display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.printf("Attempt %d/3", attempt + 1); display.display(); delay(1000); // Retry after 1 second if the request fails } if (!dataFetched) { display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.printf("Failed to fetch weather"); display.display(); http.end(); return false; // Return false if the fetch fails after 3 attempts } // Request forecast for (attempt = 0; attempt < 3; attempt++) { // Request forecast http.begin(client, "http://api.openweathermap.org/data/2.5/forecast?q=" + Location + "&APPID=" + API_Key + "&cnt=3"); int httpCode = http.GET(); if (httpCode > 0) { String payload = http.getString(); DynamicJsonDocument forecastDoc(1024); DeserializationError error = deserializeJson(forecastDoc, payload); if (!error) { int pos = 69; for (int day = 0; day <= 2; day++) { int state = forecastDoc["list"][day]["weather"][0]["id"]; // Determine icon based on state ID if (state >= 200 && state <= 232) { icon = 6; // Thunderstorm } else if (state >= 300 && state <= 321) { icon = 5; // Drizzle } else if (state >= 500 && state <= 531) { icon = 3; // Rain } else if (state >= 600 && state <= 622) { icon = 4; // Snow } else if (state >= 701 && state <= 781) { icon = 2; // Mist } else if (state == 800) { icon = 0; // Clear } else if (state >= 801 && state <= 804) { icon = 1; // Clouds } else { icon = 0; // Default icon } display.drawBitmap(pos, 0, bitmap_icons[icon], 16, 16, WHITE); pos += 20; display.display(); } http.end(); break; // Exit the loop after successful fetch } } // Display attempt number for forecast display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.printf("Forecast attempt %d/3", attempt + 1); display.display(); delay(1000); // Retry after 1 second if the request fails } if (!dataFetched) { display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.printf("Failed to fetch forecast"); display.display(); return false; // Return false if the forecast fetch fails after 3 attempts } return true; // Return true if both data fetches were successful } void setup(void) { initSystems(); netman.start(); timeClient.begin(); timeClient.update(); fetchWeatherData(); } void loop() { timeClient.update(); if (display_override && (millis() - lastDisplayOverride > displayOverrideTimeout)){ display_override = false; lastDisplayOverride = millis(); } if (!is_display_off && (millis() - lastWeatherUpdate > weatherUpdateInterval)) { fetchWeatherData(); lastWeatherUpdate = millis(); } powerSaveCheck(); commonButtonHandler(); }