Is the weather outside delightful or frightful?  With the ESP32-S2 TFT, you can design a small weather monitor that displays the existing temperature, the highest and lowest temperatures of the day, and the current weather condition.

Using the ESP32-S2 TFT WiFi capabilities, you will be able to use the OpenWeatherMap API and extract weather information. Once you've examined the information, you can showcase the current temperature, location, and daily highs and lows.

Parts

Adafruit ESP32-S2 TFT Feather powered on by a USB- C power source displaying the product tittle in a red, yellow, green, white and blue.
We've got a new machine here at Adafruit, it can uncover your deepest desires. Don't believe me? I'll turn it on right now to prove it to you! What, you want unlimited...
$24.95
In Stock
Lithium Ion Polymer Battery 3.7v 2000mAh with JST 2-PH connector
Lithium-ion polymer (also known as 'lipo' or 'lipoly') batteries are thin, light, and powerful. The output ranges from 4.2V when completely charged to 3.7V. This...
$12.50
In Stock
USB Type A to Type C Cable - approx 1 meter / 3 ft long
As technology changes and adapts, so does Adafruit. This  USB Type A to Type C cable will help you with the transition to USB C, even if you're still...
$4.95
In Stock

CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.

CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your board.

Click the link above to download the latest CircuitPython UF2 file.

Save it wherever is convenient for you.

Plug your board into your computer, using a known-good data-sync cable, directly, or via an adapter if needed.

Double-click the reset button (highlighted in red above), and you will see the RGB status LED(s) turn green (highlighted in green above). If you see red, try another port, or if you're using an adapter or hub, try without the hub, or different adapter or hub.

For this board, tap reset and wait for the LED to turn purple, and as soon as it turns purple, tap reset again. The second tap needs to happen while the LED is still purple.

If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!

A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.

You will see a new disk drive appear called FTHRS2BOOT.

 

Drag the adafruit_circuitpython_etc.uf2 file to FTHRS2BOOT.

The BOOT drive will disappear and a new disk drive called CIRCUITPY will appear.

That's it!

Open Weather Maps API Key

We'll be using OpenWeatherMaps to retrieve the weather info through its API. To do so, you'll need to register for an account and get your API key.

Go to this link and register for a free account. Once registered, you'll get an email containing your API key, known as the "openweather token."

The Mini Weather Station depends on you adding your WiFi and OpenWeatherMaps token to fetch JSON weather data.

Plug your Feather board into your computer via a known good data + power USB cable. Your board should show up as a thumb drive in your File Explorer / Finder (depending on your operating system) named CIRCUITPY.

Create a file with the name settings.toml in the root directory of the CIRCUITPY drive.

Add the following below:

The file should contain the keys CIRCUITPY_WIFI_SSID,  CIRCUITPY_WIFI_PASSWORD, CIRCUITPY_WEB_API_PASSWORD,  and OPENWEATHER_KEY. You must also include your state and country initials with the LOCATION key. 

Once these are defined, CircuitPython will automatically connect to the network and start the webserver used for the workflow.

The web server is on port 80 unless overridden by CIRCUITPY_WEB_API_PORT. It also enables MDNS.

Here is an example settings.toml:

# To auto-connect to WiFi
CIRCUITPY_WIFI_SSID="YOUR-WIFI-NETWORK-NAME"
CIRCUITPY_WIFI_PASSWORD="YOUR-WIFI-NETWORK-PASSWORD"

# For OpenWeather API
OPENWEATHER_KEY="A-LONG-STRING-OF-INTS-AND-CHARACTERS"
LOCATION="New York, US"

# To enable modifying files from the web. Change this too!
# Leave the User field blank in the browser.
CIRCUITPY_WEB_API_PASSWORD="passw0rd"

CIRCUITPY_WEB_API_PORT=80

wearables_IMG_1660.png
You can quickly transfer this project to your device using the PyLeap app.

PyLeap is a free app available for iOS, iPad, and Android devices. It can be downloaded from the Apple App Store or Google Play Store. It allows users to easily download code files and assets and transfer them to their Adafruit devices using Bluetooth Low Energy (BLE) or WiFi.

 

To upload a project to your PyLeap-enabled device, select the project in the project list. 

Then, once the project cell has collapsed, press the "Run it" button to download and transfer the project over to your PyLeap-enabled device.

# SPDX-FileCopyrightText: 2023 Trevor Beaton for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import os
import ssl
import time
import wifi
import board
import displayio
import terminalio
import socketpool
import adafruit_requests

from adafruit_display_text import bitmap_label

# Initialize Wi-Fi connection
try:
    wifi.radio.connect(
        os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
    )
    print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID"))
# Wi-Fi connectivity fails with error messages, not specific errors, so this except is broad.
except Exception as e:  # pylint: disable=broad-except
    print(
        "Failed to connect to WiFi. Error:", e, "\nBoard will hard reset in 30 seconds."
    )


# Create a socket pool and session object for making HTTP requests
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

# Set location and units for weather data
UNITS = "metric"
LOCATION = os.getenv("LOCATION")
print("Getting weather for {}".format(LOCATION))

# Set up the URL for fetching weather data
DATA_SOURCE = (
    "http://api.openweathermap.org/data/2.5/weather?q="
    + LOCATION
    + "&units="
    + UNITS
    + "&mode=json"
    + "&appid="
    + os.getenv("OPENWEATHER_KEY")
)

# Define time interval between requests
time_interval = 3000  # set the time interval to 30 minutes

# Set up display a default image
display = board.DISPLAY
bitmap = displayio.OnDiskBitmap("/images/sunny.bmp")
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)

group = displayio.Group()
group.append(tile_grid)

# Create label for displaying temperature data
text_area = bitmap_label.Label(terminalio.FONT, scale=3)
text_area.anchor_point = (0.5, 0.5)
text_area.anchored_position = (display.width // 2, display.height // 2)

# Create main group to hold all display groups
main_group = displayio.Group()
main_group.append(group)
main_group.append(text_area)
# Show the main group on the display
display.show(main_group)

# Define function to get the appropriate weather icon
def get_weather_condition_icon(weather_condition):
    if "cloud" in weather_condition.lower():
        return "/images/cloudy.bmp"
    elif "rain" in weather_condition.lower():
        return "/images/rain.bmp"
    elif "snow" in weather_condition.lower():
        return "/images/snowy.bmp"
    elif "clear" in weather_condition.lower():
        return "/images/sunny.bmp"
    else:
        return "/images/sunny.bmp"


# Define function to update the background image based on weather conditions
def set_background(weather_condition, background_tile_grid):
    bitmap_path = get_weather_condition_icon(weather_condition)
    background_bitmap = displayio.OnDiskBitmap(bitmap_path)
    background_tile_grid.bitmap = background_bitmap


# Main loop to continuously fetch and display weather data
while True:

    # Fetch weather data from OpenWeatherMap API
    print("Fetching json from", DATA_SOURCE)
    response = requests.get(DATA_SOURCE)
    print(response.json())

    # Extract temperature and weather condition data from API response
    current_temp = response.json()["main"]["temp"]
    max_temp = response.json()["main"]["temp_max"]
    min_temp = response.json()["main"]["temp_min"]
    current_weather_condition = response.json()["weather"][0]["main"]

    print("Weather condition: ", current_weather_condition)

    # Convert temperatures to Fahrenheit
    max_temp = (max_temp * 9 / 5) + 32
    min_temp = (min_temp * 9 / 5) + 32
    current_temp = (current_temp * 9 / 5) + 32

    # Convert temperatures to Fahrenheit to Celsius
    # max_temp = (max_temp - 32) * 5/9
    # min_temp = (min_temp - 32) * 5/9
    # current_temp = (current_temp - 32) * 5/9
    print("Current temperature: {:.1f} °F".format(current_temp))

    # Update label for displaying temperature data
    text_area.text = "{}\n     {:.0f}°F\nH:{:.0f}°F   L:{:.0f}°F".format(
    LOCATION, round(current_temp), round(max_temp), round(min_temp))

    # Update background image
    set_background(current_weather_condition, tile_grid)

    time.sleep(time_interval)

This code is doing several things:

  • Connecting to the internet via WiFi using your credentials
  • Create a label for displaying temperature data
  • Set up the background image for the current weather condition
  • Fetch weather data from OpenWeatherMap API
  • Extract temperature and weather condition data from the API response

Connecting to WiFi

This code block connects to a WiFi network using the wifi.radio.connect function and passes in the network’s SSID and password as arguments from your credentials. The values of the SSID and password are read from environment variables CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD. 

try:
    wifi.radio.connect(
        os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
    )
    print("Connected to %s!" % os.getenv("CIRCUITPY_WIFI_SSID"))
except Exception as e:  # pylint: disable=broad-except
    print(
        "Failed to connect to WiFi. Error:", e, "\nBoard will hard reset in 30 seconds.")

If you haven't set up your credentials, go back here.

Creating a Socket Pool

The socketpool.SocketPool function creates a pool of sockets for managing network connections. It takes the wifi.radio object as an argument to allow for network communication over a WiFi connection.

You will also need to create session object for making HTTP requests using adafruit_requests.Session.

pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

Set up the URL for fetching weather data

Set a variable for the DATA_SOURCEwhich will be used to query Open Weather Maps API next. This query will use the LOCATION and UNITS variables to form the request. The value of the OPENWEATHER_KEY is read from the environment variable OPENWEATHER_KEY located in your settings.toml file. 

DATA_SOURCE = (
    "http://api.openweathermap.org/data/2.5/weather?q="
    + LOCATION
    + "&units="
    + UNITS
    + "&mode=json"
    + "&appid="
    + os.getenv("OPENWEATHER_KEY")
)

Using this information, the code can then send a query to Open Weather Maps's API that looks something like this:

http://api.openweathermap.org/data/2.5/weather?q=New York, US&units=metric&mode=json&appid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Adding a time interval 

Create a time_interval variable to have a set time to have our weather station fetch weather data every 30 seconds.

# Define time interval between requests
time_interval = 3000  # set the time interval to 30 minutes

Setting the background image

First, create a variable called display that refers to the display object provided by the board module. This allows you to interact with the display hardware.

Then, create a variable called bitmap that refers to the displayio.OnDiskBitmap() function that creates a bitmap object from an image file stored on disk. You'll add the image file /images/sunny.bmp as a default image.

For the third line of code, create a tile_grid variable that refers to the displayio.TileGrid() function that creates a grid of tiles to display the bitmap on the display.

In the fourth line, create a group object that will have a reference to a Group container that will append the tile_grid. A Group is a container object that can hold multiple display objects and allows you to manipulate them as a single unit.

display = board.DISPLAY
bitmap = displayio.OnDiskBitmap("/images/sunny.bmp")
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)

group = displayio.Group()
group.append(tile_grid)

Setting up a label for displaying temperature data

Here you'll create a label for displaying temperature data, add it to a main group, and shows the main group on display.

Create a Label object called text_area using a built-in font from the terminalio module and sets its scale to 3 times the default size.

Next, set the label's anchor_point and anchored_position for the second and third lines. Set anchor_point to the center of the Label using a tuple of (0.5, 0.5).

The anchored_position is the position of the anchor point relative to the display. Set to the center of the display using the // operator to divide the display width and height by 2.

text_area = bitmap_label.Label(terminalio.FONT, scale=3)
text_area.anchor_point = (0.5, 0.5)
text_area.anchored_position = (display.width // 2, display.height // 2)

Next, creates a display group called main_group to hold both the tile_grid and the Label. The Tile Grid and Label are added to the group using the append method.

Finally, the last line in this code block shows the main group on the display by calling the show method of the display object and passing in the main group as an argument.

main_group = displayio.Group()
main_group.append(group)
main_group.append(text_area)
# Show the main group on the display
display.show(main_group)

This function, called get_weather_condition_icon(), takes a weather condition as input and returns a file path for the corresponding image to display its background image. We'll use this in the set_background function.

def get_weather_condition_icon(weather_condition):
    if "cloud" in weather_condition.lower():
        return "/images/cloudy.bmp"
    elif "rain" in weather_condition.lower():
        return "/images/rain.bmp"
    elif "snow" in weather_condition.lower():
        return "/images/snowy.bmp"
    elif "clear" in weather_condition.lower():
        return "/images/sunny.bmp"
    else:
        return "/images/sunny.bmp"

This function called set_background() takes two parameters: a weather condition and a tile grid for the background image. The function updates the background image based on the weather condition by replacing the bitmap used by the tile grid. These functions will be used in the main loop.

def set_background(weather_condition, background_tile_grid):
    bitmap_path = get_weather_condition_icon(weather_condition)
    background_bitmap = displayio.OnDiskBitmap(bitmap_path)
    background_tile_grid.bitmap = background_bitmap

The Main Loop

Fetching weather data from OpenWeatherMap API

This line of code sends a GET request to the OpenWeatherMap API using the requests library and retrieves the response as a JSON object. I'll also print this JSON data to see what information is coming in from the console.

print("Fetching json from", DATA_SOURCE)
response = requests.get(DATA_SOURCE)
print(response.json())

These values are then converted from Kelvin to Fahrenheit, and the converted current temperature is printed to the console for debugging purposes.

current_temp = response.json()["main"]["temp"]
max_temp = response.json()["main"]["temp_max"]
min_temp = response.json()["main"]["temp_min"]
current_weather_condition = response.json()["weather"][0]["main"]

print("Weather condition: ", current_weather_condition)

# Convert temperatures to Fahrenheit
max_temp = (max_temp * 9 / 5) + 32
min_temp = (min_temp * 9 / 5) + 32
current_temp = (current_temp * 9 / 5) + 32

# Convert temperatures to Fahrenheit to Celsius
# max_temp = (max_temp - 32) * 5/9
# min_temp = (min_temp - 32) * 5/9
# current_temp = (current_temp - 32) * 5/9
print("Current temperature: {:.1f} °F".format(current_temp))

Update label for displaying temperature data

text_area.text = "{}\n     {:.0f}°F\nH:{:.0f}°F   L:{:.0f}°F".format(
LOCATION, round(current_temp), round(max_temp), round(min_temp))

Finally, the code sleeps for a fixed interval before fetching and displaying updated weather data.

set_background(current_weather_condition, tile_grid)

time.sleep(time_interval)

Having Problems Getting Data?

If you have problems getting the data to display correctly, check your settings. The settings.toml file has the information noted here.

This guide was first published on Mar 14, 2023. It was last updated on Mar 14, 2023.