Using Huginn to get today's weather report.

Aug 03 2019

A common task that people using Huginn set up as their "Hello, world!" project is getting the daily weather report because it's practical, easy, and fairly well documented.  However, the existing example is somewhat obsolete because it references the Weather Underground API that no longer exists, having been sunset at the end of 2018.  Recently, the Weather Underground code in the Huginn Weather Agent was taken out because it's no longer usable.  But, other options exist.  The US National Weather Service has a free to use API that we can use with Huginn with a little extra work.  Here's what we have to do:

  • Get the GPS coordinates for the place we want weather reports for.
  • Use the GPS coordinates to get data out of the NWS API.
  • Build a weather report message.
  • E-mail it.

As happens sometimes, the admins of the NWS API have imposed an additional constraint upon users accessing their data: They ask that the user agent string of whatever software you use be unique, and ideally include an e-mail address they can contact you through in case something goes amiss.  This isn't a big deal.

This tutorial assumes that you've worked with Huginn a bit in the past, but if you haven't I strongly suggest that you read my earlier posts to familiarize yourself.

Okay.  Let's get started.

Per usual, the Sources field of each agent is the name of the last agent you created.  The first agent will not have one, but it will be set to execute every morning at 7:00am in the Schedule field.

Right off the bat, let's make things organized.  After logging into Huginn go to the very top of the page and click on Scenarios.  Scroll to the bottom and click on New Scenario.  Type a name for the scenario you're going to put your agents in (I used Demo Weather Forecaster).  Click Save Scenario.

Let's use as our example the city of Washington, DC.  We know from Wikipedia that the GPS coordinates of DC are 38° 54′ 17″ by 77° 0′ 59″ W.  However, we need to convert these coordinates into decimal degrees for use with the API.  So, if we click on the GPS coordinates on Wikipedia it will take us to this page where we find the coordinates as decimal numbers: 38.904722, -77.016389.  Perfect.

Step one: Create a new Website Agent by hovering on Agents at the top of the page and clicking New Agent.  Click in the Type field and scroll until you find Website Agent, then click on it.  The Weather API documentation says that we access the URL https://api.weather.gov/points/ and we give the GPS coordinates as decimal values separated with a comma.  We also manually specify a user agent that the admins could use to e-mail us if they had to.  The agent looks like this:

{
  "expected_update_period_in_days": "365",
  "url": "https://api.weather.gov/points/38.904722,-77.016389",
  "type": "json",
  "mode": "merge",
  "extract": {
    "url": {
      "path": "$.properties.forecast"
    }
  },
  "user_agent": "Exocortex v0.4.1 Weather Forecast Bot (contact: huginn-test-user at huginn dot example dot com)"
}

The reason we know what JSONpath to use is because, when I was building this agent network originally I opened the URL in my web browser and figured out what part I needed ahead of time.  We also know from the API docs that if you want to get a weather forecast you actually need to access a URL that looks like this: https://api.weather.gov/gridpoints/[ letters ]/[ a number ],[ another number ]

If we click over to the "Raw Data" view (because I'm using Firefox), select all of the data with control+a, and paste it into a JSONpath explorer we can look at the output for a string that fits the above pattern.  Once we find it we can figure out the JSONpath that extracts it.  Some scrolling around and experimentation gives us this JSONpath: $.properties.forecast

The URL that we get is: https://api.weather.gov/gridpoints/LWX/96,71/forecast

The output of that JSONpath is emitted in an event under the key "url" to the next agent in the network.  If we open that URL in our web browser to test it we get a lot of JSON data, but we'll worry about that in a moment.  Save this Website Agent and create another one.

{
  "expected_update_period_in_days": "365",
  "url": "{{ url }}",
  "type": "json",
  "mode": "merge",
  "extract": {
    "today": {
      "path": "$.properties.periods.[0].detailedForecast"
    },
    "tonight": {
      "path": "$.properties.periods.[1].detailedForecast"
    },
    "datestamp": {
      "path": "$.properties.updateTime"
    }
  },
  "user_agent": "Exocortex v0.4.1 Weather Forecast Bot (contact: huginn-test-user at huginn dot example dot com)"
}

You can see that the url field is referencing the key {{ url }} in the event it just received.  This means that this Website Agent will make a request to the URL in that value.  Once again, we figure out what to put in the extract{} part by loading the URL (the /LWX/96,71/forecast one above) in our web browser ahead of time, copying the raw data into a JSONpath evaluator again, and exploring the data to find what we're looking for (something that looks like a weather forecast or weather report).  There's a lot of data in there that we don't need right now (but might find a use for later) so we have to look carefully.  Eventually the following things will make themselves known:

  • $.properties appears to hold the good stuff.  Let's concentrate there.
  • $.properties.periods is an array that appears to hold weather reports for a couple of days.
  • The values of startTime and endTime appear to be when those weather reports are for.
  • Each entry in $.properties.periods has a key called name which says when the report is for (Tonight, Sunday, Sunday Night, etc).  That's easier than making sense of the startTime and endTime values...
  • $.properties.periods is a list of entries that appears stable, i.e. the entries are always in the same order, so we can pick the ones we want and we'll always get what we expect.  $.properties.periods.[0] will always be "Today" or "Tonight," $.properties.periods.[1] will always be 12 hours later ("Tonight" or "Tomorrow"), and so forth.
  • Each weather forecast entry in $.properties.periods has the expected temperature and temperatureUnit, windSpeed and windDirection (nice to know but we're not going to worry about those right now), and a detailed weather forecast (detailedForecast) that tells us eactly what we want to know.

In this example we care about today's weather forecast ($.properties.periods.[0].detailedForecast), tonight's expected weather forecast ($.properties.periods.[1].detailedForecast) and when the report was generated for the sake of reference ($.properties.updateTime).  To make building our weather report easy we reference those JSONpaths as "today," "tonight," and "datestamp."  Save this agent, then create a new Event Formatting Agent.

{
  "instructions": {
    "message": "As of {{ datestamp | date: '%A, %d %B %Y' }} at {{ datestamp | date: '%l:%M%p (%z)' | strip }}, the National Weather Service forecast for {{ location }} is: {{ today }}{% line_break %}Tonight's forecast is: {{ tonight }}"
  },
  "mode": "clean"
}

To make the datestamp values easier to read when we get our weather report we reformat them a little with the Liquid template library's date: operator.  The first one means this:

  • %A - The full name of the day ("Saturday")
  • %d - Day of the month (03)
  • %B - The full name of the month ("August")
  • %Y - The year (2019)

The second one translates thus:

  • %l - The hour (7)
  • : - Just a : symbol
  • %M - The minute (00)
  • %p - AM or PM ("AM")
  • %z - The timezone, as an offset from UTC (-0800 for the west coast)

If you want to know more about how and why this works, I refer you to the documentation I used when originally building this agent.  Thankfully it's somewhat standardized.

The value of "message" is what our weather report will look like when we get it.  We try to make it as easy to read and interpret as we can because it's a weather report, not War and Peace.  We're trying to make life a little simpler, after all.  Save this agent, then create an Email Agent.

{
  "subject": "Your daily weather forecast for Washington, DC",
  "body": "{{ message }}",
  "expected_receive_period_in_days": "7"
}

The only noteworthy change is the addition of the "body" tag which takes as its value a field from the incoming event from Event Formatting Agent {{ message }}, which is the weather report you just built.  Whenever an event hits this agent it'll build an outbound e-mail to you and then send it.  Save this agent, then click on Scenarios at the very top, the name of the scenario, and then View Diagram at the bottom of the page.  You will now see a map of your weather forecasting agent network.

Acid test time: Click on the very first agent in the network to open it up.  Click on the Actions menu, then Run.  Here we go.  This will schedule the first agent to run and, depending on how many other agents are in your Huginn install it will take about four minutes to run.

And then you'll find this in the e-mail address you configured Huginn with.

If you would like to play with an existing agent network, I've checked the agent network we just built into Github.  Enjoy!