diff --git a/docs/gifs/weather.gif b/docs/gifs/weather.gif index ac44bb4..d6a16f7 100644 Binary files a/docs/gifs/weather.gif and b/docs/gifs/weather.gif differ diff --git a/examples/weather/README.md b/examples/weather/README.md index 0c825b3..8fc65a8 100644 --- a/examples/weather/README.md +++ b/examples/weather/README.md @@ -32,9 +32,10 @@ Edit the constants at the top of `src/main.cpp` to customize: ## Usage - Power on the device—it will connect to WiFi, detect timezone, and start displaying weather and time. - The display shows: - - Weather label and description (e.g., "Clear sky") + - Current time in HH:MM format (e.g., "13:03") + - Current date (e.g., "Dec 8") + - Weather description (e.g., "Clear sky") - Temperature in Fahrenheit (e.g., "45 F") - - Time in HH:MM format (e.g., "13:03") - No user controls needed; it's fully automatic. ## Notes diff --git a/examples/weather/src/main.cpp b/examples/weather/src/main.cpp index 91a86b7..19a780e 100644 --- a/examples/weather/src/main.cpp +++ b/examples/weather/src/main.cpp @@ -12,9 +12,12 @@ Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); #define GAME_WIDTH 64 #define GAME_HEIGHT 128 +// Update to your WiFi creds! +// Update to your WiFi creds!WiFi const char* ssid = "Wokwi-GUEST"; const char* pass = ""; -// Hardcoded for Omaha, NE +// Update to your location! +// Default for Omaha, NE const float latitude = 41.2565; const float longitude = -95.9345; @@ -24,6 +27,9 @@ const float longitude = -95.9345; int gmtOffset_sec = -6 * 3600; int daylightOffset_sec = 3600; +// Time format: true for 12-hour, false for 24-hour +bool use12HourFormat = true; + void detectTimezone() { if (WiFi.status() != WL_CONNECTED) return; @@ -51,6 +57,82 @@ String weatherDescription = ""; float temperature = 0.0; String currentTime = ""; +int drawCenteredText(String text, int y, int textSize, int maxWidth) { + display.setTextSize(textSize); + display.setTextColor(SSD1306_WHITE); + + int16_t x1, y1; + uint16_t w, h; + + display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + + // Calculate width using character count + int charWidth = text.length() * 6 * textSize; + // Use the larger of the two widths for the wrapping check + int checkW = (charWidth > w) ? charWidth : w; + + if (checkW <= maxWidth) { + int x = (maxWidth - w) / 2; + display.setCursor(x, y); + display.println(text); + return h; + } + + // Split text into words + String words[10]; + int wordCount = 0; + int startIndex = 0; + int spaceIndex; + + while ((spaceIndex = text.indexOf(' ', startIndex)) != -1 && wordCount < 10) { + words[wordCount++] = text.substring(startIndex, spaceIndex); + startIndex = spaceIndex + 1; + } + if (startIndex < text.length() && wordCount < 10) { + words[wordCount++] = text.substring(startIndex); + } + + // Build lines without breaking words + String lines[5]; // Max 5 lines + int lineCount = 0; + String currentLine = ""; + + for (int i = 0; i < wordCount; i++) { + String testLine = currentLine + (currentLine.length() > 0 ? " " : "") + words[i]; + + // Check width of test line using conservative estimate + int testW = testLine.length() * 6 * textSize; + + if (testW <= maxWidth) { + currentLine = testLine; + } else { + if (currentLine.length() > 0) { + lines[lineCount++] = currentLine; + currentLine = words[i]; + } else { + lines[lineCount++] = words[i]; + currentLine = ""; + } + } + } + + if (currentLine.length() > 0 && lineCount < 5) { + lines[lineCount++] = currentLine; + } + + // Draw each line centered + int currentY = y; + for (int i = 0; i < lineCount; i++) { + display.getTextBounds(lines[i], 0, 0, &x1, &y1, &w, &h); + int x = (maxWidth - w) / 2; + display.setCursor(x, currentY); + display.println(lines[i]); + currentY += h + 2; + } + + return currentY - y; +} + String getWeatherDescription(int code) { switch (code) { case 0: return "Clear sky"; @@ -85,10 +167,87 @@ String getWeatherDescription(int code) { } } +void showBootScreen() { + display.clearDisplay(); + + // Show device32 boot logo with border + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + String bootText = "device32"; + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(bootText, 0, 0, &x1, &y1, &w, &h); + + int textX = (GAME_WIDTH - w) / 2; + int textY = (GAME_HEIGHT - h) / 2; + + // Draw border around text + int padding = 6; + int rectX = textX - padding; + int rectY = textY - padding; + int rectW = w + (padding * 2); + int rectH = h + (padding * 2); + display.drawRoundRect(rectX, rectY, rectW, rectH, 3, SSD1306_WHITE); + + display.setCursor(textX, textY); + display.println(bootText); + + display.display(); + delay(800); +} + void connectToWiFi() { WiFi.begin(ssid, pass); + + int step = 0; + unsigned long lastDotUpdate = 0; + while (WiFi.status() != WL_CONNECTED) { - delay(1000); + unsigned long currentMillis = millis(); + + // Update dots every 500ms + if (currentMillis - lastDotUpdate >= 500) { + display.clearDisplay(); + + int dotCount = step % 4; + String connectingText = "WiFi"; + for (int i = 0; i < dotCount; i++) { + connectingText += "."; + } + + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(connectingText, 0, 0, &x1, &y1, &w, &h); + + int textX = (GAME_WIDTH - w) / 2; + int textY = (GAME_HEIGHT - h) / 2; + + String maxText = "WiFi..."; + display.getTextBounds(maxText, 0, 0, &x1, &y1, &w, &h); + int maxTextX = (GAME_WIDTH - w) / 2; + + int padding = 4; + int rectX = maxTextX - padding; + int rectY = textY - padding; + int rectW = w + (padding * 2); + int rectH = h + (padding * 2); + display.drawRoundRect(rectX, rectY, rectW, rectH, 2, SSD1306_WHITE); + + display.setCursor(textX, textY); + display.println(connectingText); + + display.display(); + + step = (step + 1) % 4; + + lastDotUpdate = currentMillis; + } + + delay(100); } } @@ -126,8 +285,21 @@ void updateTime() { return; } char buffer[20]; - strftime(buffer, sizeof(buffer), "%H:%M", &timeinfo); - currentTime = String(buffer); + if (use12HourFormat) { + strftime(buffer, sizeof(buffer), "%I:%M", &timeinfo); + currentTime = String(buffer); + // Remove leading zero from hour + if (currentTime[0] == '0') { + currentTime = currentTime.substring(1); + } + } else { + strftime(buffer, sizeof(buffer), "%H:%M", &timeinfo); + currentTime = String(buffer); + // Remove leading zero from hour + if (currentTime[0] == '0') { + currentTime = currentTime.substring(1); + } + } } void setup() { @@ -140,6 +312,9 @@ void setup() { display.clearDisplay(); display.display(); + // Show boot screen + showBootScreen(); + connectToWiFi(); detectTimezone(); @@ -170,48 +345,117 @@ void loop() { display.clearDisplay(); display.drawRoundRect(0, 0, GAME_WIDTH, GAME_HEIGHT, 4, SSD1306_WHITE); - // Weather on top + // Time on top display.setTextSize(1); display.setTextColor(SSD1306_WHITE); int16_t x1, y1; uint16_t w, h; - String weatherLabel = "Weather:"; + String timeLabel = "Time"; + display.getTextBounds(timeLabel, 0, 0, &x1, &y1, &w, &h); + int timeLabelY = 5; + int timeBoxW = 56; + int timeBoxPadding = 2; + int timeBoxX = (GAME_WIDTH - timeBoxW) / 2; + int timeBoxY = timeLabelY - timeBoxPadding; + int timeBoxH = h + (timeBoxPadding * 2); + display.fillRoundRect(timeBoxX, timeBoxY, timeBoxW, timeBoxH, 2, SSD1306_WHITE); + + int timeLabelX = timeBoxX + (timeBoxW - w) / 2; + display.setTextColor(SSD1306_BLACK); + display.setCursor(timeLabelX, timeLabelY); + display.println(timeLabel); + display.setTextColor(SSD1306_WHITE); + + display.setTextSize(2); + display.getTextBounds(currentTime, 0, 0, &x1, &y1, &w, &h); + int timeX = (GAME_WIDTH - w) / 2; + display.setCursor(timeX, 20); + display.println(currentTime); + + display.setTextSize(1); + String dateLabel = "Date"; + display.getTextBounds(dateLabel, 0, 0, &x1, &y1, &w, &h); + int dateLabelY = 40; + int dateBoxW = 56; + int dateBoxPadding = 2; + int dateBoxX = (GAME_WIDTH - dateBoxW) / 2; + int dateBoxY = dateLabelY - dateBoxPadding; + int dateBoxH = h + (dateBoxPadding * 2); + display.fillRoundRect(dateBoxX, dateBoxY, dateBoxW, dateBoxH, 2, SSD1306_WHITE); + + int dateLabelX = dateBoxX + (dateBoxW - w) / 2; + display.setTextColor(SSD1306_BLACK); + display.setCursor(dateLabelX, dateLabelY); + display.println(dateLabel); + display.setTextColor(SSD1306_WHITE); + + // Get current date + struct tm timeinfo; + if (getLocalTime(&timeinfo)) { + char dateBuffer[20]; + strftime(dateBuffer, sizeof(dateBuffer), "%a %b %d", &timeinfo); + String currentDate = String(dateBuffer); + // Remove leading zero from day + int spacePos = currentDate.lastIndexOf(' '); + if (spacePos != -1 && currentDate[spacePos + 1] == '0') { + currentDate.remove(spacePos + 1, 1); + } + // Uppercase only the first letter of the month + int firstSpace = currentDate.indexOf(' '); + int secondSpace = currentDate.indexOf(' ', firstSpace + 1); + if (firstSpace != -1 && secondSpace != -1) { + currentDate[firstSpace + 1] = toupper(currentDate[firstSpace + 1]); + } + + display.getTextBounds(currentDate, 0, 0, &x1, &y1, &w, &h); + int dateX = (GAME_WIDTH - w) / 2; + display.setCursor(dateX, 55); + display.println(currentDate); + } + + display.setTextSize(1); + String weatherLabel = "Weather"; display.getTextBounds(weatherLabel, 0, 0, &x1, &y1, &w, &h); - int x = (GAME_WIDTH - w) / 2; - display.setCursor(x, 10); + int weatherLabelY = 68; + int weatherBoxW = 56; + int boxPadding = 2; + int boxX = (GAME_WIDTH - weatherBoxW) / 2; + int boxY = weatherLabelY - boxPadding; + int boxH = h + (boxPadding * 2); + display.fillRoundRect(boxX, boxY, weatherBoxW, boxH, 2, SSD1306_WHITE); + + int weatherLabelX = boxX + (weatherBoxW - w) / 2; + display.setTextColor(SSD1306_BLACK); + display.setCursor(weatherLabelX, weatherLabelY); display.println(weatherLabel); + display.setTextColor(SSD1306_WHITE); String desc = weatherDescription.length() == 0 ? "Loading weather..." : weatherDescription; - display.getTextBounds(desc, 0, 0, &x1, &y1, &w, &h); - x = (GAME_WIDTH - w) / 2; - display.setCursor(x, 25); - display.println(desc); + if (desc.length() > 25) desc = desc.substring(0, 25) + "..."; + int weatherHeight = drawCenteredText(desc, 85, 1, GAME_WIDTH); - String tempStr = (temperature == 0.0 && weatherDescription.length() == 0) ? "" : String((int)temperature) + " F"; + String tempStr = (temperature == 0.0 && weatherDescription.length() == 0) ? "" : String((int)temperature); if (tempStr != "") { + int tempY = 80 + weatherHeight + 8; + if (weatherHeight <= 10) tempY += 6; display.setTextSize(2); - display.getTextBounds(tempStr, 0, 0, &x1, &y1, &w, &h); - x = (GAME_WIDTH - w) / 2; - display.setCursor(x, 45); - display.println(tempStr); + uint16_t numW, fW, th; + display.getTextBounds(tempStr, 0, 0, &x1, &y1, &numW, &th); + display.setTextSize(1); + display.getTextBounds("F", 0, 0, &x1, &y1, &fW, &th); + int totalW = numW + fW; + int startX = (GAME_WIDTH - totalW) / 2; + display.setTextSize(2); + display.setCursor(startX, tempY); + display.print(tempStr); + display.setTextSize(1); + display.setCursor(startX + numW + 2, tempY + 6); + display.print("F"); display.setTextSize(1); } - // Time on bottom - String timeLabel = "Time:"; - display.getTextBounds(timeLabel, 0, 0, &x1, &y1, &w, &h); - x = (GAME_WIDTH - w) / 2; - display.setCursor(x, 85); - display.println(timeLabel); - - display.setTextSize(2); - display.getTextBounds(currentTime, 0, 0, &x1, &y1, &w, &h); - x = (GAME_WIDTH - w) / 2; - display.setCursor(x, 100); - display.println(currentTime); - display.display(); delay(1000); -} +} \ No newline at end of file