Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/gifs/weather.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions examples/weather/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
306 changes: 275 additions & 31 deletions examples/weather/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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() {
Expand All @@ -140,6 +312,9 @@ void setup() {
display.clearDisplay();
display.display();

// Show boot screen
showBootScreen();

connectToWiFi();

detectTimezone();
Expand Down Expand Up @@ -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);
}
}