# 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.root_group = 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 setting the root_group property of the display object to the main_group.

main_group = displayio.Group()
main_group.append(group)
main_group.append(text_area)
# Show the main group on the display
display.root_group = 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 Apr 16, 2024.

This page (PyLeap Mini Weather Station Code) was last updated on Apr 16, 2024.

Text editor powered by tinymce.