Cookie Notice

As far as I know, and as far as I remember, nothing in this page does anything with Cookies.
Showing posts with label weather. Show all posts
Showing posts with label weather. Show all posts

2017/07/07

Temperature based on Current Location for the Mobile Computer


I'm always curious about how people customize their prompt. I put name and machine in, with color-coding based on which machine, because while spend most of my time on one or two hosts, I have reason to go to several others.

I work in a sub-basement, and for most of my work days, I couldn't tell you if it was summer and warm, winter and frozen, or spring and waterlogged outside, so one of the things I learned to check out is current weather information. I used to put the temperature on the front panel of our HP printer, but we've moved to Xerox.

Currently, I use DarkSky, formerly forecast.io. I know my meteorologist friends would recommend more primary sources, but I've always found it easy to work with.

I had this code talking to Redis, but I decided that it was an excuse to use Redis and this data was better suited for storing in YAML, so I rewrote it.

store_temp
#!/usr/bin/env perl

# stores current temperature with YAML so that get_temp.pl can be used
# in the bash prompt to display current temperature

use feature qw{ say state } ;
use strict ;
use warnings ;

use Carp ;
use Data::Dumper ;
use DateTime ;

use IO::Interactive qw{ interactive } ;
use JSON ;
use LWP::UserAgent ;
use YAML::XS qw{ DumpFile LoadFile } ;

my $config = config() ;
my $url
    = 'https://api.darksky.net/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 $now = DateTime->now()->set_time_zone('America/New_York')->datetime() ;
    my $content  = $response->content ;
    my $forecast = decode_json $content ;
    my $current  = $forecast->{currently} ;
    my $temp_f   = int $current->{temperature} ;
    store( $now, $temp_f ) ;
    }
else {
    say $response->status_line ;
    }

exit ;

# ======================================================================
sub store { my ( $time, $temp ) = @_ ; say {interactive} qq{Current Time: $time} ; say {interactive} qq{Current Temperature: $temp} ; my $data_file = $ENV{HOME} . '/.temp.yaml' ; my $obj = { curr_time => $time, curr_temp => $temp, } ; DumpFile( $data_file, $obj ) ; } # ====================================================================== # Reads configuration data from YAML file. Dies if no valid config file # if no other value is given, it will choose current # # Shows I need to put this into a module sub config { my $config_file = $ENV{HOME} . '/.forecast.yaml' ; my $output = {} ; if ( defined $config_file && -f $config_file ) { my $output = LoadFile($config_file) ; $output->{current} = 1 ; return $output ; } croak('No Config File') ; }

And this is the code that reads the YAML and prints it, nice and short and ready to be called in PS1.

get_temp
#!/usr/bin/env perl

# retrieves the current temperature from YAML to be used in the bash prompt

use feature qw{ say state unicode_eval unicode_strings } ;
use strict ;
use warnings ;
use utf8 ;
binmode STDOUT, ':utf8' ;

use Carp ;
use Data::Dumper ;
use YAML::XS qw{ LoadFile } ;

my $data_file = $ENV{HOME} . '/.temp.yaml' ;
my $output    = {} ;
if ( defined $data_file && -f $data_file ) {
    my $output = LoadFile($data_file) ;
    print $output->{curr_temp} . '°F' || '' ;
    exit ;
    }
croak('No Temperature File') ;

I thought I put a date-diff in there. I wanted to be able say 'Old Data' if the update time was too long ago. I should change that.

I should really put the config files in __DATA__ for show, because it will show that the location is hard-coded. For a desktop or server, that makes sense; it can only go as far as the power plug stretches. But, for other reasons, I adapted my bash prompt on my Linux laptop, and I recently took it to another state, so I'm thinking more and more that I need to add a step, to look up where I am before I check the temperature.

store_geo_temp
#!/usr/bin/env perl

# Determines current location based on IP address using Google
# Geolocation, finds current temperature via the DarkSky API
# and stores it into a YAML file, so that get_temp.pl can be
# in the bash prompt to display current local temperature.

use feature qw{ say state } ;
use strict ;
use warnings ;
use utf8 ;

use Carp ;
use Data::Dumper ;
use DateTime ;
use IO::Interactive qw{ interactive } ;
use JSON::XS ;
use YAML::XS qw{ DumpFile LoadFile } ;

use lib $ENV{HOME} . '/lib' ;
use GoogleGeo ;

my $json     = JSON::XS->new->pretty->canonical ;
my $config   = config() ;
my $location = geolocate( $config->{geolocate} ) ;
croak 'No Location Data' unless $location->{lat} ;

my $forecast = get_forecast( $config, $location ) ;
croak 'No Location Data' unless $forecast->{currently} ;

say {interactive} $json->encode($location) ;
say {interactive} $json->encode($forecast) ;

my $now     = DateTime->now()->set_time_zone('America/New_York')->datetime() ;
my $current = $forecast->{currently} ;
my $temp_f  = int $current->{temperature} ;
store( $now, $temp_f ) ;

exit ;

# ======================================================================
# Reads configuration data from YAML files. Dies if no valid config files
sub config {
    my $geofile = $ENV{HOME} . '/.googlegeo.yaml' ;
    croak 'no Geolocation config' unless -f $geofile ;
    my $keys = LoadFile($geofile) ;

    my $forecastfile = $ENV{HOME} . '/.forecast.yaml' ;
    croak 'no forecast config' unless -f $forecastfile ;
    my $fkeys = LoadFile($forecastfile) ;
    $keys->{forecast} = $fkeys->{apikey} ;
    croak 'No forecast key' unless $keys->{forecast} ;
    croak 'No forecast key' unless $keys->{geolocate} ;
    return $keys ;
    }

# ======================================================================
# Takes the config for the API keys and the location, giving us lat and lng
# returns the forecast object or an empty hash if failing
sub get_forecast {
    my ( $config, $location ) = @_ ;
    my $url
        = 'https://api.darksky.net/forecast/'
        . $config->{forecast} . '/'
        . ( join ',', map { $location->{$_} } qw{ lat lng } ) ;
    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 ;
        return $forecast ;
        }
    return {} ;
    }

# ======================================================================
sub store { my ( $time, $temp ) = @_ ; say {interactive} qq{Current Time: $time} ; say {interactive} qq{Current Temperature: $temp} ; my $data_file = $ENV{HOME} . '/.temp.yaml' ; my $obj = { curr_time => $time, curr_temp => $temp, } ; DumpFile( $data_file, $obj ) ; }

A few things I want to point out here. First off, you could write this with Getopt::Long and explicit quiet and verbose flags, but Perl and IO::Interactive allow me to make this context-specific and implicit. If I run it myself, interactively, I am trying to diagnose issues, and that's when say {interactive} works. If I run it in crontab, then it runs silently, and I don't get an inbox filled with false negatives from crontab. This corresponds to my personal preferences; If I was to release this to CPAN, I would likely make these things controlled by flags, and perhaps allow latitude, longitude and perhaps API keys to be put in that way.

But, of course, you should not get in the habit, because then your keys show up in the process table. It's okay if you're the only user, but not best practice.

This is the part that's interesting. I need to make it better/strong/faster/cooler before I put it on CPAN, maybe something like Google::Geolocation or the like. Will have to read some existing Google-pointing modules on MetaCPAN before committing. Geo::Google looks promising, but it doesn't do much with "Where am I now?" work, which is exactly what I need here.

Google's Geolocation API works better when you can point to access points and cell towers, but that's diving deeper than I need; the weather will be more-or-less the same across the widest accuracy variation I could expect.

GoogleGeo
package GoogleGeo ;

# interfaces with Google Geolcation API 

# https://developers.google.com/maps/documentation/geolocation/intro

use feature qw{say} ;
use strict ;
use warnings ;

use Carp ;
use Data::Dumper ;
use Exporter qw(import) ;
use Getopt::Long ;
use JSON::XS ;
use LWP::Protocol::https ;
use LWP::UserAgent ;

our @EXPORT = qw{
    geocode
    geolocate
    } ;

my $json  = JSON::XS->new->pretty ;
my $agent = LWP::UserAgent->new ;

sub geocode {
    my ($Google_API_key,$obj) = @_ ;
    croak unless defined $Google_API_key ;
    my $url = 'https://maps.googleapis.com/maps/api/geocode/json?key='
        . $Google_API_key ;
    my $latlng = join ',', $obj->{lat}, $obj->{lng} ;
    $url .= '&latlng=' . $latlng ;
    my $object = { latlng => $latlng } ;
    my $r = $agent->post($url) ;
    if ( $r->is_success ) {
        my $j = $r->content ;
        my $o = $json->decode($j) ;
        return $o ;
        }
    return {} ;
    }

sub geolocate {
    my ($Google_API_key) = @_ ;
    my $url = 'https://www.googleapis.com/geolocation/v1/geolocate?key='
        . $Google_API_key ;
    my $object = {} ;
    my $r = $agent->post( $url, $object ) ;
    if ( $r->is_success ) {
        my $j = $r->content ;
        my $o = $json->decode($j) ;
        return {
            lat => $o->{location}{lat},
            lng => $o->{location}{lng},
            acc => $o->{accuracy},
            } ;
        }
    return {} ;
    }

'here' ;

If this has been helpful or interesting to you, please tell me so in the comments.

2015/05/04

Putting Current Temperature into my Bash Prompt

my life as drawn by phdcomics.com
I work in a sub-basement. I've been lead to understand that this is desirable for us because the lab equipment is on the solid cement of the foundation, so that we don't have to worry about footsteps shaking it, but I honestly don't know.

The problem at least for me, is that it I'm well-isolated from the outside world, so that it could just as easily 9° or 90° outside, and I wouldn't know. I've felt apprehension to wander upstairs, only to find that, once I got there, it wasn't all that bad.

With a previous printer, I would write the current conditions to the front-panel LCD, because Perl, but we've moved from HP to Xerox, and I no longer have that ability. I now have it tell the time and temp at the top and bottom of the hour, to keep from being so deep in the zone that I think it's lunchtime when it's really quittin' time. I also had code to generate notify balloons, but a recent switch from Ubuntu to Debian made that unusable.

I've been playing with NoSQL databases and have found that I spend most of my time with MongoDB, but once I started thinking about the weather problem — I need it to be available to any of my n terminals, I don't need structure — I decided that Redis was the way to go.

Each NoSQL database works in a different way. MongoDB works as a Document Store, holding a piece of structured data until you call it back. I have code to record daily status updates and send it to bosses where each day holds an array full of objects holding a timestamp and status report, and at 6pm, if the day's array holds data, it formats that data and sends the mail. This is more structure than I really need in this context.

What I really need is essentially a hash table that I can access from many different programs. If it falls down, that's fine; I don't need to remember what the temperature was at 4am, I just need to know what it is now, or within the last 10 minutes. Redis works like that, and (unless you have it commit to disc), it's only in memory, so fast. (I think; I've only played with Redis, not put production data into it.) I think this feature would be good in, for example, Raspberry Pi uses, where excessive access could prematurely kill your SD card.

Anyway, here's my code to store the temperature, to pull it back out, and a replacement $PS1 to show you how to add it to your prompt. It backends to forecast.io, my most recent go-to for weather information. The API is very useful and easy to use. (I don't put "Powered by Forecast" in my prompt but I do put it in the program that uses notify-osd and Pushover.) Use it in good weather.

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.


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!

2012/08/28

What do I do with my weather stuff now?

It started with me wanting to know how to code with R. R is about handling and graphing data, and I didn't really have data of my own. So, what I did was find a source I liked for weather and started putting it into a MySQL database.

That source was Google, specifically the XML used for weather in the soon-to-be-closed iGoogle.

Here's that code.

#!/usr/bin/perl

# $Id: hpsetdisp.pl 11 2006-03-22 01:21:03Z yaakov $

# hpsetdisp.pl
# Connects to a JetDirect equipped HP printer and uses
# HP's control language to set the ready message on the
# LCD display.  Takes an IP address and message on the
# command line. My favorite message is "INSERT COIN".
# Keep in mind the limitations of the display when composing
# your clever verbiage.
#
# THIS PROGRAM IS PROVIDED WITH NO WARRANTY OF ANY KIND EXPRESSED OR IMPLIED
# THE AUTHOR CANNOT BE RESPONSIBLE FOR THE EFFECTS OF THIS PROGRAM
# IF YOU ARE UNCERTAIN ABOUT THE ADVISABILITY OF USING IT, DO NOT!
#
# Yaakov (http://kovaya.com/ )
#
# Modified by Garrett Hoofman
# 10/18/2007
# http://www.visionsofafar.com

# Modified by David Jacoby
# 12/10/2008
# http://varlogrant.blogspot.com/

# Modified by David Jacoby, using notify-send and tweaking Unicode
# 09/08/2008
# http://varlogrant.blogspot.com/

# Modified by David Jacoby, moving from Google to NOAA
# 08/28/2012
# http://varlogrant.blogspot.com/


use strict ;
use warnings ;
use Modern::Perl ;
use DBI ;
use Getopt::Long ;
use IO::Interactive qw{ interactive } ;
use IO::Socket ;
use XML::DOM ;
use Data::Dumper ;

use lib '/home/jacoby/lib' ;
use MyDB 'db_connect' ;

use subs qw{  get_weather notify db_upload } ;

my $zip         = '' ;
my $interactive = '' ;
my $degree = '°' ; #the unicode, straight
   $degree = "\x{00b0}" ; #the unicode, specified
my $rdymsg      = "Ready" ;
my $curr_cond ;

GetOptions(
    'zipcode=s'   => \$zip ,
    ) ;

$zip !~ /\d/ and exit 0 ;
my %weather = get_weather ;
db_upload %weather ;

exit ;

########## ########## ########## ########## ########## ########## ##########
sub get_weather {
    my %output ;
    my $parser = XML::DOM::Parser->new() ;
    my $file   = 'http://www.google.com/ig/api?weather=XXXXX&hl=en' ;
    #my $zip    = shift ;
    $zip =~ s{(\d{5})}{}mx ;
    $zip = $1 ;
    $file =~ s/XXXXX/$zip/mx ;
    my $doc = $parser->parsefile( $file ) ;
    exit if ! defined $doc ;
    $output{zip}        = $zip ;
    $output{city}       = $doc->getElementsByTagName( 'city' )->item( 0 )
                        ->getAttribute('data');
    $output{temp_c}     = $doc->getElementsByTagName( 'temp_c' )->item( 0 )
                        ->getAttribute('data');
    $output{temp_f}     = $doc->getElementsByTagName( 'temp_f' )->item( 0 )
                        ->getAttribute('data');
    $output{humidity}   = $doc->getElementsByTagName( 'humidity' )->item( 0 )
                        ->getAttribute('data');
    $output{wind}       = $doc->getElementsByTagName( 'wind_condition' )->item( 0 )
                        ->getAttribute('data');
    $output{conditions} = $doc->getElementsByTagName( 'condition' )->item( 0 )
                        ->getAttribute('data') ;

    return %output ;
    }
########## ########## ########## ########## ########## ########## ##########

########## ########## ########## ########## ########## ########## ##########
sub db_upload {
    my %cond = @_ ;
    my $dbh = db_connect() ;
    for my $k ( sort keys %cond ) {
        $cond{$k} = $dbh->quote( $cond{$k} ) ;
        }
    my $fields = join ',' , qw{
        city        conditions
        humidity    temp_c
        temp_f      wind
        zip
        } ;

    my $sql ;
    my $rows ;

    $sql = qq{
    INSERT INTO weather_data ( $fields ) VALUES (
        $cond{ city } ,
        $cond{ conditions } ,
        $cond{ humidity } ,
        $cond{ temp_c } ,
        $cond{ temp_f } ,
        $cond{ wind } ,
        $cond{ zip }
        ) } ;
    #$rows = $dbh->do( $sql ) or croak $dbh->errstr;

    $sql = qq{
    INSERT INTO weather ( $fields ) VALUES (
        $cond{ city } ,
        $cond{ conditions } ,
        $cond{ humidity } ,
        $cond{ temp_c } ,
        $cond{ temp_f } ,
        $cond{ wind } ,
        $cond{ zip }
        ) } ;
    $rows = $dbh->do( $sql ) or croak $dbh->errstr;

    return ;
    }
########## ########## ########## ########## ########## ########## ##########

Problem is, Google knows I've been grabbing weather data every 10 minutes and has blocked me.

While I fill in all these, I am not pulling in weather data from multiple cities, so I can hardcode city and zip, and I couldjust drop conditions and wind. All I'm really watching is temp_f, and it's a simple conversion to get temp_c if I wanted it.

I have code that pulls from NOAA. Think I'll try to bring that in here.

2009/09/23

Desperately Seeking Google Weather API Vocabulary

I work in a sub-basement. From about 9am to about 5pm, I don't see the sun. Great amounts of change can happen in that time, and I hate coming up for air without knowing what's up. So, great amounts of my programming come from trying to keep track of conditions. I send the weather info to local printers. I have a twitter feed. I have gravitated to using XML::DOM and Google's Weather API to give me the information. It's not too detailed, it's fast, and if Google's down, the Internet is down.

I have since found notify-send (mentioned here) and have found a set of weather icons that I like. And I'm working on getting the notifications look good.

My problem is that I'm not seeing a one-to-one mapping between the condition strings I'm getting from Google and the file names I'm getting from the icon sets. Nor are the image names listed in the XML.

So, I created a simple mapping of Condition strings to icon names.

sub config_icons {
my $path = '/home/jacoby/Pictures/weather_icons/' ;
my %config ;
$config{ 'Clear' } = $path . 'sunny.png' ;
$config{ 'Partly Cloudy' } = $path . 'cloudy1.png' ;
$config{ 'Mostly Cloudy' } = $path . 'cloudy3.png' ;
$config{ 'Cloudy' } = $path . 'cloudy5.png' ;
$config{ 'Overcast' } = $path . 'overcast.png' ;
$config{ 'Haze' } = $path . 'fog.png' ;
$config{ 'Fog' } = $path . 'fog.png' ;
$config{ 'ELSE' } = $path . 'dunno.png' ;
return \%config ;
}
This is a list of weather conditions that were observable in the American Midwest in summer and early fall, 2009. This is not the whole list of weather conditions.

What I am looking for today are the other ones, so I can finish this code and maybe send it out to you. Any help?