Skip to main content

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

1

User Activation

User triggers with “what’s the weather” or similar
2

City Collection

Asks which city to check
3

Geocoding

Converts city name to coordinates using Nominatim (OpenStreetMap)
4

Weather Fetch

Queries Open-Meteo API for current conditions
5

Report Delivery

Speaks a natural weather report with temperature and wind speed

API Requirements

No API keys required. Uses free public APIs.

APIs Used

Nominatim

Free geocoding service from OpenStreetMap. Converts city names to lat/lon coordinates.nominatim.openstreetmap.org

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

Build docs developers (and LLMs) love