WiFi Weather Station

Create a connected weather display that shows current conditions and forecasts from online weather APIs.

Intermediate2-3 hoursIot
WiFi Weather Station

Required Components:

MatriXDeck V1WiFi Connection

WiFi Weather Station

Weather Station

Introduction

Build a connected weather station that displays real-time weather data and forecasts using your MatriXDeck! This project demonstrates how to use the ESP32's WiFi capabilities to fetch data from online weather APIs and display it in an attractive, easy-to-read format on the LED matrix.

This project will teach you about:

  • Making HTTP requests to web APIs
  • Parsing JSON data
  • Creating custom weather icons and animations
  • Building an intuitive user interface on a small display

Materials Needed

  • MatriXDeck V1
  • WiFi internet connection
  • OpenWeatherMap API key (free tier)
  • USB-C cable for power (or use the battery)

Project Overview

We'll create a weather station with the following features:

  • Current temperature, humidity, and conditions
  • Daily forecast for the next few days
  • Weather condition animations (rain, snow, sun, clouds)
  • Automatic location detection based on IP (or customizable location)
  • Cycling display between different weather information

Step-by-Step Instructions

1. Setting Up WiFi and API Key

First, let's set up the project with WiFi connectivity and our API key:

#include <MatriXDeck.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <time.h>

MatriXDeck matrix;

// WiFi credentials
const char* ssid = "YourWiFiName";
const char* password = "YourWiFiPassword";

// OpenWeatherMap API settings
const char* apiKey = "your_openweathermap_api_key";
const char* city = "London";  // Default city
const char* countryCode = "uk";  // Default country code
String units = "metric";  // Use "imperial" for Fahrenheit

// Time between API updates (10 minutes = 600000 ms)
// Free OpenWeatherMap account allows 60 calls per minute
const unsigned long updateInterval = 600000;
unsigned long lastUpdateTime = 0;

// Weather data variables
float temperature, feelsLike, minTemp, maxTemp;
int humidity, pressure;
String weatherMain, weatherDescription;
String weatherIcon;

2. Connecting to WiFi and Setting Up

Next, let's implement the setup function:

void setup() {
  matrix.begin();
  
  // Display connecting message
  matrix.clear();
  matrix.print("WiFi", 8, 3, GREEN);
  matrix.print("...", 12, 11, GREEN);
  matrix.update();
  
  // Connect to WiFi
  WiFi.begin(ssid, password);
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    attempts++;
  }
  
  if (WiFi.status() != WL_CONNECTED) {
    // WiFi connection failed
    matrix.clear();
    matrix.print("WiFi", 2, 3, RED);
    matrix.print("Failed", 2, 11, RED);
    matrix.update();
    delay(3000);
  } else {
    // WiFi connected successfully
    matrix.clear();
    matrix.print("Connected!", 0, 8, GREEN);
    matrix.update();
    delay(2000);
    
    // Set up time (needed for HTTPS connections)
    configTime(0, 0, "pool.ntp.org");
    
    // Get initial weather data
    getWeatherData();
  }
}

3. Getting Weather Data from API

Now, let's implement the function to fetch weather data:

void getWeatherData() {
  // Display loading message
  matrix.clear();
  matrix.print("Loading", 0, 4, YELLOW);
  matrix.print("Weather", 0, 12, YELLOW);
  matrix.update();
  
  // Make sure WiFi is still connected
  if (WiFi.status() != WL_CONNECTED) {
    matrix.clear();
    matrix.print("WiFi", 2, 3, RED);
    matrix.print("Error", 2, 11, RED);
    matrix.update();
    return;
  }
  
  // Create HTTP client
  HTTPClient http;
  
  // Build the API URL (current weather data)
  String url = "http://api.openweathermap.org/data/2.5/weather?q=";
  url += String(city) + "," + String(countryCode);
  url += "&units=" + units;
  url += "&appid=" + String(apiKey);
  
  // Start the request
  http.begin(url);
  int httpCode = http.GET();
  
  if (httpCode > 0) {
    // Successful response
    if (httpCode == HTTP_CODE_OK) {
      // Parse JSON response
      String payload = http.getString();
      
      // Use ArduinoJson to parse the response
      DynamicJsonDocument doc(1024);
      DeserializationError error = deserializeJson(doc, payload);
      
      if (!error) {
        // Extract weather data
        temperature = doc["main"]["temp"];
        feelsLike = doc["main"]["feels_like"];
        minTemp = doc["main"]["temp_min"];
        maxTemp = doc["main"]["temp_max"];
        humidity = doc["main"]["humidity"];
        pressure = doc["main"]["pressure"];
        
        weatherMain = doc["weather"][0]["main"].as<String>();
        weatherDescription = doc["weather"][0]["description"].as<String>();
        weatherIcon = doc["weather"][0]["icon"].as<String>();
        
        // Update successful, store timestamp
        lastUpdateTime = millis();
      } else {
        // JSON parsing error
        matrix.clear();
        matrix.print("JSON", 8, 3, RED);
        matrix.print("Error", 6, 11, RED);
        matrix.update();
        delay(2000);
      }
    }
  } else {
    // HTTP error
    matrix.clear();
    matrix.print("API", 10, 3, RED);
    matrix.print("Error", 6, 11, RED);
    matrix.update();
    delay(2000);
  }
  
  http.end();
}

4. Displaying Weather Information

Let's create functions to display the weather data in an attractive format:

// Variables for display state
int displayMode = 0;  // 0 = current, 1 = details, 2 = forecast
unsigned long lastDisplayChange = 0;
const unsigned long displayChangeInterval = 5000;  // Change display every 5 seconds

void loop() {
  // Check if it's time to update the weather data
  if (millis() - lastUpdateTime >= updateInterval) {
    getWeatherData();
  }
  
  // Cycle through display modes
  if (millis() - lastDisplayChange >= displayChangeInterval) {
    displayMode = (displayMode + 1) % 3;
    lastDisplayChange = millis();
  }
  
  // Display appropriate content based on mode
  matrix.clear();
  
  switch (displayMode) {
    case 0:
      displayCurrentWeather();
      break;
    case 1:
      displayWeatherDetails();
      break;
    case 2:
      displayForecastAnimation();
      break;
  }
  
  matrix.update();
  delay(100);
}

void displayCurrentWeather() {
  // Draw temperature in large font
  String tempString = String((int)round(temperature)) + ((units == "metric") ? "C" : "F");
  matrix.print(tempString, 1, 8, YELLOW);
  
  // Draw appropriate weather icon
  drawWeatherIcon(22, 8);
}

void displayWeatherDetails() {
  // Show humidity and feels like temperature
  matrix.print("HUM:", 0, 2, GREEN);
  matrix.print(String(humidity) + "%", 0, 9, YELLOW);
  
  matrix.print("FEEL:", 20, 2, GREEN);
  matrix.print(String((int)round(feelsLike)), 22, 9, YELLOW);
}

void displayForecastAnimation() {
  // Display weather condition animation
  // This will vary based on weather conditions
  
  static int frame = 0;
  
  if (weatherMain == "Rain") {
    drawRainAnimation(frame);
  } else if (weatherMain == "Snow") {
    drawSnowAnimation(frame);
  } else if (weatherMain == "Clear") {
    drawSunAnimation(frame);
  } else if (weatherMain == "Clouds") {
    drawCloudAnimation(frame);
  } else {
    // Default animation for other weather types
    matrix.print(weatherMain, 0, 4, GREEN);
  }
  
  frame = (frame + 1) % 8;  // 8 frames of animation
}

5. Creating Weather Icons and Animations

Now let's implement the functions to draw weather icons and animations:

void drawWeatherIcon(int x, int y) {
  // Draw different icons based on weather condition
  if (weatherMain == "Clear") {
    // Sun icon
    matrix.drawCircle(x, y, 3, YELLOW);
    matrix.setPixel(x, y-5, YELLOW);  // Top ray
    matrix.setPixel(x, y+5, YELLOW);  // Bottom ray
    matrix.setPixel(x-5, y, YELLOW);  // Left ray
    matrix.setPixel(x+5, y, YELLOW);  // Right ray
    matrix.setPixel(x-4, y-4, YELLOW);  // Top-left ray
    matrix.setPixel(x+4, y+4, YELLOW);  // Bottom-right ray
    matrix.setPixel(x-4, y+4, YELLOW);  // Bottom-left ray
    matrix.setPixel(x+4, y-4, YELLOW);  // Top-right ray
  } 
  else if (weatherMain == "Clouds") {
    // Cloud icon
    matrix.fillRect(x-3, y, 7, 3, GREEN);
    matrix.fillRect(x-1, y-1, 3, 1, GREEN);
  }
  else if (weatherMain == "Rain") {
    // Rain icon
    matrix.fillRect(x-3, y-2, 7, 2, GREEN);  // Cloud
    matrix.setPixel(x-2, y+1, YELLOW);  // Rain drop
    matrix.setPixel(x, y+2, YELLOW);    // Rain drop
    matrix.setPixel(x+2, y+1, YELLOW);  // Rain drop
  }
  else if (weatherMain == "Snow") {
    // Snow icon
    matrix.fillRect(x-3, y-2, 7, 2, GREEN);  // Cloud
    matrix.setPixel(x-2, y+1, GREEN);  // Snowflake
    matrix.setPixel(x, y+2, GREEN);    // Snowflake
    matrix.setPixel(x+2, y+1, GREEN);  // Snowflake
  }
  else if (weatherMain == "Thunderstorm") {
    // Thunderstorm icon
    matrix.fillRect(x-3, y-2, 7, 2, GREEN);  // Cloud
    matrix.setPixel(x, y+1, YELLOW);    // Lightning
    matrix.setPixel(x, y+2, YELLOW);    // Lightning
    matrix.setPixel(x+1, y+3, YELLOW);  // Lightning
  }
  else {
    // Generic icon for other conditions
    matrix.fillRect(x-2, y-1, 5, 3, GREEN);
  }
}

void drawRainAnimation(int frame) {
  // Draw cloud
  matrix.fillRect(8, 2, 16, 3, GREEN);
  
  // Draw rain drops at different positions based on frame
  for (int i = 0; i < 6; i++) {
    int xPos = 10 + i*3;
    int yPos = 6 + ((i + frame) % 4) * 2;
    
    if (yPos < 15) {  // Only draw if within screen bounds
      matrix.setPixel(xPos, yPos, YELLOW);
    }
  }
  
  // Add temperature at bottom
  String tempString = String((int)round(temperature));
  matrix.print(tempString + ((units == "metric") ? "C" : "F"), 1, 1, YELLOW);
}

void drawSnowAnimation(int frame) {
  // Draw cloud
  matrix.fillRect(8, 2, 16, 3, GREEN);
  
  // Draw snowflakes at different positions based on frame
  for (int i = 0; i < 5; i++) {
    int xPos = 8 + i*4;
    int yPos = 6 + ((i + frame) % 4) * 2;
    
    if (yPos < 15) {  // Only draw if within screen bounds
      // Draw a small cross for each snowflake
      matrix.setPixel(xPos, yPos, GREEN);
      matrix.setPixel(xPos-1, yPos, GREEN);
      matrix.setPixel(xPos+1, yPos, GREEN);
      matrix.setPixel(xPos, yPos-1, GREEN);
      matrix.setPixel(xPos, yPos+1, GREEN);
    }
  }
  
  // Add temperature at bottom
  String tempString = String((int)round(temperature));
  matrix.print(tempString + ((units == "metric") ? "C" : "F"), 1, 1, YELLOW);
}

void drawSunAnimation(int frame) {
  // Draw the sun
  int centerX = 16;
  int centerY = 8;
  int radius = 4;
  
  // Draw the sun center
  matrix.drawCircle(centerX, centerY, radius, YELLOW);
  matrix.setPixel(centerX, centerY, YELLOW);
  
  // Draw sun rays that "pulse" based on frame
  int rayLength = 2 + (frame % 2);
  
  // Draw 8 rays
  for (int i = 0; i < 8; i++) {
    float angle = i * PI / 4.0;
    int x1 = centerX + cos(angle) * (radius + 1);
    int y1 = centerY + sin(angle) * (radius + 1);
    int x2 = centerX + cos(angle) * (radius + rayLength);
    int y2 = centerY + sin(angle) * (radius + rayLength);
    
    matrix.drawLine(x1, y1, x2, y2, YELLOW);
  }
  
  // Add temperature at bottom
  String tempString = String((int)round(temperature));
  matrix.print(tempString + ((units == "metric") ? "C" : "F"), 1, 1, YELLOW);
}

void drawCloudAnimation(int frame) {
  // Draw moving clouds
  int offset = frame % 8;
  
  // Draw multiple clouds that move across the screen
  matrix.fillRect(offset, 4, 8, 3, GREEN);
  matrix.fillRect(offset+2, 3, 4, 1, GREEN);
  
  matrix.fillRect(offset+16, 6, 8, 3, GREEN);
  matrix.fillRect(offset+18, 5, 4, 1, GREEN);
  
  // Add temperature at bottom
  String tempString = String((int)round(temperature));
  matrix.print(tempString + ((units == "metric") ? "C" : "F"), 1, 1, YELLOW);
}

Complete Code

The complete code combines all these elements and adds more features like button controls to change cities and forecast display options. You can find the full code in our GitHub repository.

API Setup Guide

To use the OpenWeatherMap API, you'll need to:

  1. Go to OpenWeatherMap and create a free account
  2. Navigate to the API Keys section and copy your API key
  3. Replace your_openweathermap_api_key in the code with your actual API key
  4. Update the city and country code to your preferred location

Extending the Project

Here are some ideas to enhance your weather station:

  1. Multi-City Support: Add the ability to cycle through multiple cities
  2. Weather Alerts: Add special displays for weather alerts and warnings
  3. Historical Data: Track and display temperature trends over time
  4. Sensor Integration: Add the Sensor Pack to display local temperature alongside online data
  5. Voice Announcements: Use the speaker to announce weather conditions

Troubleshooting

Not receiving weather data

  • Verify your API key is correct
  • Check that your WiFi connection is stable
  • Ensure the city name and country code are valid

Data not updating

  • The free OpenWeatherMap API has rate limits; make sure you're not exceeding them
  • Check for error messages on the display

Display issues

  • Adjust the display layout if text appears cut off
  • For very long city names, consider abbreviating or scrolling text

Conclusion

Congratulations! You've created a WiFi-connected weather station with your MatriXDeck. This project demonstrates how to connect to web APIs, process JSON data, and create an intuitive visual interface.

By creating this weather station, you've developed skills that can be applied to many other IoT projects. Your MatriXDeck can connect to various online services, making it a versatile display for all kinds of real-time data.

Consider expanding this project by adding other data sources or creating a dashboard that cycles through weather, news headlines, stock prices, and more!


Share your weather station customizations with us on our Discord community or tag us on social media with #MatriXDeckWeather!