A Raspberry Pi, three LEDs, and a weather forecast are all you need to build a simple frost warning system.
In this tutorial, we’ll use the Visual Crossing Weather API to retrieve forecast minimum temperatures for a location. A Python script running on the Raspberry Pi will analyze the forecast and illuminate one of three LEDs:
- Red: freezing temperatures are forecast
- Yellow: cold temperatures are forecast, but not freezing
- Green: no cold temperatures are forecast
The project provides a simple example of combining live weather data with Raspberry Pi GPIO hardware.
We’ll use the Visual Crossing Timeline Weather API, Python 3, and the GPIO Zero library recommended by Raspberry Pi for controlling GPIO devices.
What you’ll need
For this project you’ll need:
- A network-connected Raspberry Pi
- Raspberry Pi OS
- Three LEDs: red, yellow, and green
- Three suitable current-limiting resistors, such as 220–330 Ω
- Breadboard and jumper wires
- Python 3
- A Visual Crossing Weather API key
If you’re new to the Visual Crossing Weather API, see:https://www.visualcrossing.com/resources/documentation/weather-api/how-do-i-get-started-with-the-weather-api/
The complete Timeline Weather API documentation is available at:https://www.visualcrossing.com/resources/documentation/weather-api/timeline-weather-api/
How the frost alert works
Our Raspberry Pi will periodically retrieve the weather forecast for a specified location.
We’ll examine the minimum temperature for the next three forecast days.
The alert status will be:
Red Minimum forecast temperature <= 32°F
Yellow Minimum forecast temperature <= 40°F
Green Minimum forecast temperature > 40°F
For metric units, the equivalent defaults are:
Red Minimum forecast temperature <= 0°C
Yellow Minimum forecast temperature <= 4°C
Green Minimum forecast temperature > 4°C
The thresholds and number of forecast days are configuration values, so you can easily adjust them for your application.
Using a defined three-day alert period is more useful than checking the entire available forecast. If freezing weather is forecast far into the future, you may not want the warning light to remain red continuously for many days.
Step 1 – Wire the LEDs to the Raspberry Pi
We’ll use three GPIO pins:
GPIO 23 – red LED
GPIO 24 – yellow LED
GPIO 25 – green LED
Each LED should be connected through a current-limiting resistor.
For example:
GPIO pin → resistor → LED → ground
Do not connect an LED directly to a GPIO output without a resistor.
You can view the GPIO layout for your particular Raspberry Pi by running:
pinout
Raspberry Pi provides additional GPIO documentation here:
https://www.raspberrypi.com/documentation/computers/raspberry-pi.html
Step 2 – Install the Python libraries
This project uses:
gpiozeroto control the LEDsrequeststo retrieve weather data
On Raspberry Pi OS, install them using:
sudo apt update
sudo apt install -y python3-gpiozero python3-requests
GPIO Zero is designed specifically to simplify physical computing projects on Raspberry Pi.
The Raspberry Pi GPIO documentation is available at:
https://www.raspberrypi.com/documentation/usage/iot
Step 3 – Create a Visual Crossing Weather API key
Create or sign in to your Visual Crossing Weather account and obtain your Weather API key.
You can learn more about the available developer services at:https://www.visualcrossing.com/weather-api/
You can also interactively explore historical and forecast datasets using Visual Crossing Weather Data.
Visual Crossing plans include usage and request limits. Your application should be designed to operate within the limits of your account plan and avoid unnecessarily repeating identical requests.
Step 4 – Store the API key securely
Rather than placing the API key directly in the Python source file, store it in an environment variable.
From the Raspberry Pi terminal:
export VISUAL_CROSSING_API_KEY="YOUR_API_KEY"
Our Python script can then read it using:
import os
API_KEY = os.environ["VISUAL_CROSSING_API_KEY"]
This helps prevent the API key from accidentally being committed to a public source-code repository.
If you configure the script to start automatically, make sure the environment variable is also available to the service or scheduled process that runs it.
Step 5 – Test the weather forecast request
The Timeline Weather API uses a simple URL structure.
For example, this request retrieves forecast data for Chicago:
https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/Chicago%2CIL?unitGroup=us&include=days&key=YOUR_API_KEY&contentType=json
For our frost alert, we don’t need every available weather field.
We only need:
datetime
tempmin
conditions
We can therefore add:
elements=datetime,tempmin,conditions
The complete request becomes:
https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/Chicago%2CIL?unitGroup=us&include=days&elements=datetime,tempmin,conditions&key=YOUR_API_KEY&contentType=json
Daily forecast records are returned in the Timeline API days array.
For example:
weather["days"][0]["tempmin"]
returns the forecast minimum temperature for the first day.
For more information about the available weather fields, see the Visual Crossing Weather Data documentation.
Step 6 – Retrieve the forecast with Python
Create a file named:
frost_alert.py
Start with the following code:
import os
import requests
API_KEY = os.environ["VISUAL_CROSSING_API_KEY"]
LOCATION = "Chicago, IL"
UNIT_GROUP = "us"
FORECAST_DAYS = 3
FREEZING_TEMP = 32
COLD_TEMP = 40
def get_weather_forecast():
url = (
"https://weather.visualcrossing.com/"
"VisualCrossingWebServices/rest/services/timeline/"
+ requests.utils.quote(LOCATION, safe="")
)
params = {
"key": API_KEY,
"unitGroup": UNIT_GROUP,
"include": "days",
"elements": "datetime,tempmin,conditions",
"contentType": "json"
}
response = requests.get(
url,
params=params,
timeout=15
)
response.raise_for_status()
return response.json()
requests.get() retrieves the forecast from the Weather API.
The params dictionary contains the query parameters that will be added to the request.
We also specify:
timeout=15
so that a temporary network problem does not cause the Raspberry Pi to wait indefinitely.
Finally:
response.raise_for_status()
causes HTTP errors to be reported rather than attempting to process an unsuccessful response as weather data.
Step 7 – Determine the frost warning level
Next, we’ll examine the first few forecast days and find the lowest predicted temperature.
Add:
def get_alert_status(weather):
days = weather["days"][:FORECAST_DAYS]
if not days:
raise ValueError("Weather API returned no forecast days")
coldest_day = min(
days,
key=lambda day: day["tempmin"]
)
lowest_temp = coldest_day["tempmin"]
if lowest_temp <= FREEZING_TEMP:
status = "freezing"
elif lowest_temp <= COLD_TEMP:
status = "cold"
else:
status = "normal"
return status, coldest_day
The expression:
weather["days"][:FORECAST_DAYS]
limits our frost analysis to the configured alert period.
Then:
min(days, key=lambda day: day["tempmin"])
finds the day with the lowest forecast minimum temperature.
This gives us both the warning status and the day responsible for the alert.
Step 8 – Control the LEDs with GPIO Zero
Now add GPIO Zero:
from gpiozero import LED
Create the three LED objects:
red_led = LED(23)
yellow_led = LED(24)
green_led = LED(25)
Then create a function that displays the current alert:
def display_alert(status):
red_led.off()
yellow_led.off()
green_led.off()
if status == "freezing":
red_led.on()
elif status == "cold":
yellow_led.on()
else:
green_led.on()
Only one status LED will be illuminated at a time.
Step 9 – Periodically update the frost forecast
Weather forecasts don’t need to be downloaded every few seconds.
For this example, we’ll refresh the forecast every 30 minutes:
UPDATE_INTERVAL = 30 * 60
You could choose a different interval depending on your application, but repeatedly downloading the same forecast every few seconds provides little benefit and unnecessarily increases API usage.
Our main loop becomes:
import time
def main():
while True:
try:
print("Retrieving weather forecast...")
weather = get_weather_forecast()
status, coldest_day = get_alert_status(
weather
)
display_alert(status)
print(
f"Status: {status}, "
f"date: {coldest_day['datetime']}, "
f"minimum temperature: "
f"{coldest_day['tempmin']}"
)
except requests.RequestException as error:
print(
"Unable to retrieve weather forecast:",
error
)
except Exception as error:
print(
"Unable to process weather forecast:",
error
)
time.sleep(UPDATE_INTERVAL)
If a Weather API or network error occurs, the program reports the error and tries again at the next scheduled update rather than terminating immediately.
Step 10 – Complete Raspberry Pi frost alert script
Here is the complete program:
import os
import time
import requests
from gpiozero import LED
API_KEY = os.environ["VISUAL_CROSSING_API_KEY"]
LOCATION = "Chicago, IL"
UNIT_GROUP = "us"
FORECAST_DAYS = 3
FREEZING_TEMP = 32
COLD_TEMP = 40
UPDATE_INTERVAL = 30 * 60
red_led = LED(23)
yellow_led = LED(24)
green_led = LED(25)
def get_weather_forecast():
url = (
"https://weather.visualcrossing.com/"
"VisualCrossingWebServices/rest/services/timeline/"
+ requests.utils.quote(LOCATION, safe="")
)
params = {
"key": API_KEY,
"unitGroup": UNIT_GROUP,
"include": "days",
"elements": "datetime,tempmin,conditions",
"contentType": "json"
}
response = requests.get(
url,
params=params,
timeout=15
)
response.raise_for_status()
return response.json()
def get_alert_status(weather):
days = weather["days"][:FORECAST_DAYS]
if not days:
raise ValueError(
"Weather API returned no forecast days"
)
coldest_day = min(
days,
key=lambda day: day["tempmin"]
)
lowest_temp = coldest_day["tempmin"]
if lowest_temp <= FREEZING_TEMP:
status = "freezing"
elif lowest_temp <= COLD_TEMP:
status = "cold"
else:
status = "normal"
return status, coldest_day
def display_alert(status):
red_led.off()
yellow_led.off()
green_led.off()
if status == "freezing":
red_led.on()
elif status == "cold":
yellow_led.on()
else:
green_led.on()
def main():
while True:
try:
print("Retrieving weather forecast...")
weather = get_weather_forecast()
status, coldest_day = get_alert_status(
weather
)
display_alert(status)
print(
f"Status: {status}, "
f"date: {coldest_day['datetime']}, "
f"minimum temperature: "
f"{coldest_day['tempmin']}"
)
except requests.RequestException as error:
print(
"Unable to retrieve weather forecast:",
error
)
except Exception as error:
print(
"Unable to process weather forecast:",
error
)
time.sleep(UPDATE_INTERVAL)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nFrost alert stopped.")
Run the script with:
python3 frost_alert.py
The appropriate LED should illuminate after the first forecast has been retrieved.
Using metric temperatures
The example above uses US units:
UNIT_GROUP = "us"
FREEZING_TEMP = 32
COLD_TEMP = 40
For metric temperatures, change these values to:
UNIT_GROUP = "metric"
FREEZING_TEMP = 0
COLD_TEMP = 4
You can also customize these thresholds to match the plants, equipment, or other conditions that you are trying to protect.
Improving the frost alert
This project intentionally uses simple daily minimum temperatures, but there are many ways to extend it.
For example, you could:
- Examine hourly temperature forecasts to estimate when freezing conditions will begin.
- Flash the red LED when freezing conditions are expected within the next few hours.
- Add a buzzer or display.
- Send an email, SMS, or push notification.
- Monitor multiple locations.
- Record forecast temperatures to a local database.
- Compare the forecast against historical weather.
- Include wind, precipitation, snow, humidity, or other weather variables.
- Use different temperature thresholds for different plants or equipment.
Hourly forecasts are available in each Timeline Weather API day’s hours array when hourly data is requested.
The same Visual Crossing Weather API can provide historical weather, current conditions, hourly forecasts, daily forecasts, weather alerts, and additional environmental data.
If you want to explore those datasets interactively rather than through code, visit Visual Crossing Weather Data.
API usage
Visual Crossing plans include usage and request limits that should be followed when building automated applications.
A frost alert does not normally need to retrieve a completely new forecast every few seconds. Choose an update interval appropriate for your application and avoid repeatedly requesting identical information when it can be reused.
See the Visual Crossing Weather API page for current API options and account plans.
Next steps
This simple frost warning system demonstrates how easily physical devices can respond to forecast weather information.
From here you can explore:
- Visual Crossing Weather API
- Visual Crossing Weather Data
- Timeline Weather API documentation
- Getting started with the Weather API
- Weather Data documentation
- Raspberry Pi GPIO documentation
The same approach can be adapted to create weather-driven irrigation systems, freeze protection monitors, outdoor equipment alerts, greenhouse controllers, and many other Raspberry Pi weather projects.

