What It Does
Fetches current weather for any city using free APIs. Reports temperature and wind speed in a natural spoken format. No API key required.
Suggested Trigger Words
“what’s the weather”
“weather report”
“check the weather”
“how’s the weather”
Key Features
Global Coverage Works for any city worldwide via geocoding
No API Key Uses completely free Open-Meteo and Nominatim APIs
Real-Time Data Fetches live temperature and wind speed
Simple Flow Ask → provide city → get weather report
How It Works
User Activation
User triggers with “what’s the weather” or similar
City Collection
Asks which city to check
Geocoding
Converts city name to coordinates using Nominatim (OpenStreetMap)
Weather Fetch
Queries Open-Meteo API for current conditions
Report Delivery
Speaks a natural weather report with temperature and wind speed
API Requirements
No API keys required. Uses free public APIs.
APIs Used
Open-Meteo Free weather API with no registration required. Provides current conditions and forecasts. open-meteo.com
Code Walkthrough
API Configuration
GEOCODE_URL = "https://nominatim.openstreetmap.org/search"
WEATHER_URL = "https://api.open-meteo.com/v1/forecast"
Geocoding Flow
Convert city name to coordinates:
# Geocode the location
geo_resp = requests.get(
GEOCODE_URL ,
params = { "q" : location, "format" : "json" , "limit" : 1 },
headers = { "User-Agent" : "OpenHome-Weather-Ability" },
)
geo_data = geo_resp.json()
if not geo_data:
await self .capability_worker.speak(
"I couldn't find that location. Try a different city name."
)
self .capability_worker.resume_normal_flow()
return
lat = geo_data[ 0 ][ "lat" ]
lon = geo_data[ 0 ][ "lon" ]
display_name = geo_data[ 0 ].get( "display_name" , location)
Weather Data Fetch
Query current conditions:
# Fetch weather
weather_resp = requests.get(
WEATHER_URL ,
params = {
"latitude" : lat,
"longitude" : lon,
"current" : "temperature_2m,wind_speed_10m" ,
},
)
weather_data = weather_resp.json()
current = weather_data.get( "current" , {})
temp = current.get( "temperature_2m" , "unknown" )
wind = current.get( "wind_speed_10m" , "unknown" )
Natural Report Generation
report = (
f "The current temperature in { location } is { temp } degrees Celsius "
f "with wind speeds of { wind } kilometers per hour."
)
await self .capability_worker.speak(report)
Error Handling
Graceful fallbacks for API failures:
try :
# ... API calls ...
except Exception as e:
self .worker.editor_logging_handler.error( f "[Weather] Error: { e } " )
await self .capability_worker.speak(
"Sorry, I couldn't get the weather right now. Try again later."
)
self .capability_worker.resume_normal_flow()
Complete Implementation
import json
import os
import requests
from src.agent.capability import MatchingCapability
from src.main import AgentWorker
from src.agent.capability_worker import CapabilityWorker
GEOCODE_URL = "https://nominatim.openstreetmap.org/search"
WEATHER_URL = "https://api.open-meteo.com/v1/forecast"
class WeatherCapability ( MatchingCapability ):
worker: AgentWorker = None
capability_worker: CapabilityWorker = None
@ classmethod
def register_capability ( cls ) -> "MatchingCapability" :
with open (
os.path.join(os.path.dirname(os.path.abspath( __file__ )), "config.json" )
) as file :
data = json.load( file )
return cls (
unique_name = data[ "unique_name" ],
matching_hotwords = data[ "matching_hotwords" ],
)
def call ( self , worker : AgentWorker):
self .worker = worker
self .capability_worker = CapabilityWorker( self .worker)
self .worker.session_tasks.create( self .get_weather())
async def get_weather ( self ):
# Ask for location
await self .capability_worker.speak( "Which city would you like the weather for?" )
location = await self .capability_worker.user_response()
if not location:
await self .capability_worker.speak( "I didn't catch that. Please try again later." )
self .capability_worker.resume_normal_flow()
return
await self .capability_worker.speak( f "Checking the weather in { location } ." )
try :
# Geocode the location
geo_resp = requests.get(
GEOCODE_URL ,
params = { "q" : location, "format" : "json" , "limit" : 1 },
headers = { "User-Agent" : "OpenHome-Weather-Ability" },
)
geo_data = geo_resp.json()
if not geo_data:
await self .capability_worker.speak(
"I couldn't find that location. Try a different city name."
)
self .capability_worker.resume_normal_flow()
return
lat = geo_data[ 0 ][ "lat" ]
lon = geo_data[ 0 ][ "lon" ]
display_name = geo_data[ 0 ].get( "display_name" , location)
# Fetch weather
weather_resp = requests.get(
WEATHER_URL ,
params = {
"latitude" : lat,
"longitude" : lon,
"current" : "temperature_2m,wind_speed_10m" ,
},
)
weather_data = weather_resp.json()
current = weather_data.get( "current" , {})
temp = current.get( "temperature_2m" , "unknown" )
wind = current.get( "wind_speed_10m" , "unknown" )
report = (
f "The current temperature in { location } is { temp } degrees Celsius "
f "with wind speeds of { wind } kilometers per hour."
)
await self .capability_worker.speak(report)
except Exception as e:
self .worker.editor_logging_handler.error( f "[Weather] Error: { e } " )
await self .capability_worker.speak(
"Sorry, I couldn't get the weather right now. Try again later."
)
self .capability_worker.resume_normal_flow()
Example Conversation
User: "What's the weather"
AI: "Which city would you like the weather for?"
User: "San Francisco"
AI: "Checking the weather in San Francisco."
AI: "The current temperature in San Francisco is 15.2 degrees Celsius
with wind speeds of 12 kilometers per hour."
You can query any city worldwide - try “Tokyo”, “London”, “São Paulo”, or even small towns!
Extending This Ability
Forecast Use Open-Meteo’s forecast API to show 7-day weather predictions
Weather Conditions Add precipitation, humidity, and weather descriptions
Temperature Units Support Fahrenheit conversion for US users
Location Memory Remember the user’s default city across sessions
Available Weather Parameters
Open-Meteo supports many more parameters. Just add them to the current parameter:
params = {
"latitude" : lat,
"longitude" : lon,
"current" : "temperature_2m,wind_speed_10m,relative_humidity_2m,precipitation,weather_code" ,
}
Full documentation: open-meteo.com/en/docs