Cookie Notice

As far as I know, and as far as I remember, nothing in this page does anything with Cookies.

2014/09/12

Leveraging the Forecast.io API

A while ago, I got an API key for Forecast.io, with the intention of writing something that takes a look at the day to say whether I could bike to work without getting weathered on. I have not written that, and I certainly haven't started biking to work on a regular basis.

But now, I have written something that uses that data and presents it via notify-send and Pushover. I use notify-send a lot, but as the sources change, I have to change my scripts.

#!/usr/bin/env perl
# forecast.pl is Copyright (C) 2014, by Dave Jacoby.
# It is free software; you can redistribute it and/or modify it under the terms of either:
#
# a) the GNU General Public License as published by the Free Software Foundation; either external linkversion 1, or (at your option) any later versionexternal link, or
#
# b) the "Artistic License".
# interface to forecast.io which uses Linux notify-send to
# send a variety of weather information to the user's awareness
# Usage:
# --current , -c cureent conditions
# --alerts, -a alerts; current weather alerts if any
# --hourly, -h hourly forecasts for the next four hours
# --daily, -d forecast for the next two days
# You can use all the flags at once. No flags defaults to current conditions
use feature qw{ say state } ;
use strict ;
use warnings ;
use utf8 ;
use Carp ;
use Data::Dumper ;
use DateTime ;
use Getopt::Long ;
use JSON ;
use LWP::UserAgent ;
use YAML qw{ LoadFile } ;
my $config = config() ;
my $url =
'https://api.forecast.io/forecast/'
. $config->{ apikey } . '/'
. ( join ',', map { $config->{ $_ } } qw{ latitude longitude } ) ;
my $agent = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 } ) ;
my $response = $agent->get( $url ) ;
if ( $response->is_success ) {
my $content = $response->content ;
my $forecast = decode_json $content ;
my $current = $forecast->{ currently } ;
my $alerts = $forecast->{ alerts } ;
my $hourly = $forecast->{ hourly } ;
my $daily = $forecast->{ daily } ;
say join "\n",
( map { join "\t", '+', $_, ref $forecast->{ $_ } } sort keys %$forecast ), ''
if $config->{ verbose } ;
current_conditions( $current ) if $config->{ current } ;
daily_forecasts( $daily ) if $config->{ daily } ;
hourly_forecasts( $hourly ) if $config->{ hourly } ;
handle_alerts( $alerts ) if $config->{ alerts } ;
}
exit ;
sub current_conditions {
my $current = shift ;
my $temp = int $current->{ temperature } ;
my $summary = $current->{ summary } ;
my $liketemp = int $current->{ apparentTemperature } ;
my $icon = $current->{ icon } ;
my $title = qq{Currently ${temp}°F} ;
my $body = qq{$summary. Feels like ${liketemp}°F.\nPowered by Forecast.} ;
say $icon if $config->{ verbose } ;
my $png = get_icons( $icon ) ;
notify( $title, $body , $png ) ;
}
sub handle_alerts {
my $alerts = shift ;
my $body = qq{Powered by Forecast.} ;
for my $alert ( @$alerts ) {
my $title = $alert->{ title } ;
my $warning = 0 ;
$warning = 1 if $title =~ /warning/i ;
notify( $title, $body ) ;
}
}
sub hourly_forecasts {
my $hourly = shift ;
my $icon = $hourly->{ icon } ;
my $summary = $hourly->{ summary } ;
my $data = $hourly->{ data } ;
my $body = $summary . "\n\n" ;
for my $hour ( @$data[ 0 .. 3 ] ) {
my $summary = $hour->{ summary } ;
my $dt = DateTime->from_epoch( epoch => $hour->{ time } ) ;
$body .= join " ", $dt->hour_12(), $dt->am_or_pm(), '-', $summary, "\n" ;
}
$body .= qq{Powered by Forecast.} ;
my $title = 'Four-Hour Forecast' ;
my $png = get_icons( $icon ) ;
notify( $title, $body , $png ) ;
}
sub daily_forecasts {
my ( $daily, $f ) = @_ ;
my $icon = $daily->{ icon } ;
my $summary = $daily->{ summary } ;
my $data = $daily->{ data } ;
my $body ;
for my $day ( @$data[ 1 .. 2 ] ) {
my $summary = $day->{ summary } ;
my $dt = DateTime->from_epoch( epoch => $day->{ time } ) ;
my $max = int $day->{ temperatureMax } ;
my $min = int $day->{ temperatureMin } ;
my $icon = $day->{ icon } ;
$body .= join ' ',
$dt->day_name() . ':',
$summary, "\n\t" ,
'H:',
$max . '°F',
'- L:',
$min . '°F',
"\n" ;
}
$body .= qq{Powered by Forecast.} ;
my $title = 'Two-Day Forecast' ;
my $png = get_icons( $icon ) ;
notify( $title, $body , $png ) ;
}
# Handles the actual notification, using Linux's notify-send
sub notify {
my $title = shift ;
my $body = shift ;
my $icon = shift ;
$body = $body || '' ;
$icon = $icon || $ENV{HOME} . '/Pictures/Icons/icon_black_muffin.jpg' ;
`notify-send "$title" "$body" -i $icon ` ;
}
# show the icons
sub get_icons {
my $icon = shift ;
my $path = $ENV{ HOME } . '/Pictures/forecast.io/' ;
# these are the icon names that forecast.io will use.
my @icons = qw{
clear-day
clear-night
cloudy
fog
partly-cloudy-day
partly-cloudy-night
rain
sleet
snow
wind
} ;
# I found my weather icon set online, which had vastly
# different icons. I cannot distribute them, as they aren't mine
# and I don't hold the copyright, but you can find/make your own.
# Coming soon?
# hail
# thunderstorm
# tornado
if ( grep m{$icon} , @icons ) {
return $path . $icon . '.png' ;
}
return $path . 'default' . '.png' ;
}
# Reads configuration data from YAML file. Dies if no valid config file
# if no other value is given, it will choose current
sub config {
my $config_file = $ENV{ HOME } . '/.forecast.yaml' ;
my $output = {} ;
if ( defined $config_file && -f $config_file ) {
my $output = LoadFile( $config_file ) ;
GetOptions(
'verbose' => \$output->{ verbose },
'alerts' => \$output->{ alerts },
'current' => \$output->{ current },
'daily' => \$output->{ daily },
'hourly' => \$output->{ hourly },
) ;
$output->{ current } = 1
if !$output->{ alerts }
and !$output->{ hourly }
and !$output->{ daily } ;
say Dumper $output if $output->{ verbose } ;
return $output ;
}
croak( 'No Config File' ) ;
}
__DATA__
What ~/.forecast.yaml should look like, set for Lafayette
---
apikey: You can't have my API key
latitude: 40.422778
longitude: -86.915278
view raw forecast.pl hosted with ❤ by GitHub

I've tried to cut things down to just the things that are necessary, but I could do more for that. Carp, for example, is Perl's way of sending errors to STDERR and exiting and such, but I really don't use it. There is a verbose flag that uses Data::Dumper. I'm turning epoch timestamps to times and dates, so I need DateTime (Thank you Dave Rolsky). The API uses JSON to send the weather data, and I use YAML to hold configuration data, so those modules are given. I want to get something from the web, so I use LWP.

So, download, use, adapt, learn, and stay dry!