Improve finding of proper update time.
[bkuhn:small-hacks.git] / conky-mythtv-weather-build.plx
1 #!/usr/bin/perl
2 # conky-mythtv-weather-build.plx
3 #
4 # Copyright (C) 2013, Bradley M. Kuhn
5 #
6 # This program gives you software freedom; you can copy, modify, convey,
7 # and/or redistribute it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 3 of the
9 # License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program in a file called 'GPLv3'.  If not, write to the:
18 #    Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
19 #                                    Boston, MA 02110-1301, USA.
20 #
21 # Quick hack to use mythtv scripts.
22
23 # Get listing of stations with:
24 # /home/bkuhn/hacks/mythtv/mythplugins/mythweather/mythweather/scripts/us_nws/nwsxml.pl  -l
25
26 use strict;
27 use warnings;
28 use Date::Manip;
29 use utf8;
30 use feature 'unicode_strings';
31 use Encode qw(encode decode);
32
33 use File::Temp qw/tempdir/;
34
35 chdir("$ENV{HOME}/tmp/.conky-mythtv-weather")
36   or die "unable to go to $ENV{HOME}/tmp/.conky-mythtv-weather";
37
38 if (@ARGV != 6 and @ARGV != 7) {
39   print STDERR "usage: $0 /path/to/mythtv/git/checkout <units> <location> <text_voffset> <img_voffset> <fontsize_pixels> [hour-format]\n";
40   exit 1;
41 }
42 my($MYTH_PATH, $UNITS, $LOCATION, $VOFFSET_TEXT, $VOFFSET_IMAGE, $FONT_SIZE, $HOUR_FORMAT) = @ARGV;
43 $HOUR_FORMAT = "%a %H:%M" unless defined $HOUR_FORMAT;
44 my $degree;
45 if ($UNITS eq "SI") {
46   $degree = encode('utf8', "°C");
47 } elsif ($UNITS eq 'ENG') {
48   $degree = encode('utf8', "°F");
49 } else {
50   die "invalid units, $UNITS";
51 }
52
53 my($mythIconPath) = $MYTH_PATH .  "/mythplugins/mythweather/theme/default/icons";
54 my(%commands) = ('forecast' => $MYTH_PATH .
55                  "/mythplugins/mythweather/mythweather/scripts/us_nws/ndfd18.pl",
56                  'extended' => $MYTH_PATH .
57                  "/mythplugins/mythweather/mythweather/scripts/us_nws/ndfd.pl",
58                  'current' => $MYTH_PATH .
59                  "/mythplugins/mythweather/mythweather/scripts/us_nws/nwsxml.pl",
60                 'accuweather' => $MYTH_PATH .
61                 "/mythplugins/mythweather/mythweather/scripts/accuweather/accuweather.pl");
62
63 my %data;
64
65 my($location1) = $LOCATION;
66 my($location2) = $LOCATION;
67 if ($LOCATION =~ /^([^|]+)\|([^|]+)/) {
68   ($location1, $location2) = ($1, $2);
69 }
70 if ($location1 eq "FORCE_ACCUWEATHER") {
71   foreach my $key (qw/forecast extended current/) { delete $commands{$key}; }
72 }
73 foreach my $type (keys %commands) {
74   my $location = $location1;
75   $location = $location2 if $type eq "accuweather";
76   open(DATA, "-|", $commands{$type}, '-u', $UNITS, $location)
77     or die "unable to run: $commands{$type} -u $UNITS $LOCATION: $!";
78
79   while (my $line = <DATA>) {
80     die "bad line output in data: $line"
81       unless $line =~ /^\s*(\S+)\s*:\s*:\s*(.*)$/;
82     $data{$type}{$1} = $2;
83   }
84   close DATA;
85   die "error($?) running: $commands{$type} -u $UNITS $LOCATION: $!"
86     unless $? == 0;
87 }
88 if (not defined $data{current}{observation_time}) {
89   foreach my $key (%{$data{accuweather}}) {
90     $data{current}{$key} = $data{accuweather}{$key};
91   }
92 }
93 if (not defined $data{forecast}{updatetime}
94     and defined $data{accuweather}{'time-1'}) {
95   foreach my $key (%{$data{accuweather}}) {
96     $data{forecast}{$key} = $data{accuweather}{$key};
97   }
98 }
99 if (not defined $data{extended}{updatetime}) {
100   foreach my $key (%{$data{accuweather}}) {
101     $data{extended}{$key} = $data{accuweather}{$key};
102   }
103 }
104
105 my $updateTime = $data{forecast}{updatetime};
106 $updateTime = $data{extended}{updatetime} if not defined $updateTime;
107
108 $updateTime =~ s/\s*(?:Last\s*)?Updated\s*(?:on|:)?\s*//;
109 my $now =  ParseDate("now");
110 $updateTime = ParseDate($updateTime);
111 my $x = Delta_Format(DateCalc($updateTime, $now), 0, "%mt minutes ago");
112
113 $data{forecast}{updatetime} = $x if defined $x;
114 $data{forecast}{updatetime} = "as of $data{forecast}{updatetime}";
115 $data{forecast}{"maxLength"} = 0;
116 my %doneDays;
117 foreach my $ii (qw/0 1 2 3 4 5/) {
118   next if not defined $data{forecast}{"time-${ii}"};
119   my $time = ParseDate($data{forecast}{"time-${ii}"});
120   next if not defined $time;
121   if (defined $time) {
122     $time = DateCalc($time, "+ 1 day") if ($time lt $updateTime);
123     if ($time lt $now) {
124       delete $data{forecast}{"time-${ii}"};
125       next;
126     }
127     my $day = UnixDate($time, '%A');
128     $data{forecast}{"time-${ii}"} = UnixDate($time, $HOUR_FORMAT);
129     my $ll = length($data{forecast}{"time-${ii}"});
130     $data{forecast}{"maxLength"} = $ll
131       unless $data{forecast}{"maxLength"} > $ll;
132     $doneDays{$day} = 'forecast';
133   }
134 }
135 my $f = $FONT_SIZE + 5;
136 print '${voffset ', $VOFFSET_TEXT , '} ${font :size=', $f, '}${alignc}Weather:${font}', " $data{current}{'cclocation'}\n";
137 if (not defined $data{current}{observation_time_rfc822}) {
138   $data{current}{observation_time_rfc822} = $data{current}{observation_time};
139   $data{current}{observation_time_rfc822} =~ s/^\s*(?:Observation\s*of\s*:?|Last\s*Updated\s*(?:on)?)\s*//;
140 }
141 my($temp, $feelsLike, $humidity, $windSpeed, $windGust, $icon, $datetime, $weatherConditions) =
142   ($data{current}{temp}, $data{current}{heat_index},
143    $data{current}{relative_humidity}, $data{current}{wind_speed},
144    $data{current}{wind_gust}, $data{current}{weather_icon},
145    $data{current}{observation_time_rfc822}, $data{current}{weather});
146
147 my $date = ParseDate($datetime);
148
149 my $howOld = DateCalc($date, $now);
150 my $ago = Delta_Format($howOld, 0, "%mt min ago");
151 my $hourFormat = $HOUR_FORMAT;
152 if ($howOld ge DateCalc($now, "+ 1 day")) {
153   $ago =  Delta_Format($howOld, 0, "\${color5}%dt day(s) ago\${color}");
154 } elsif (UnixDate($date, "%Y-%m-%d") ne UnixDate($now, "%Y-%m-%d")) {
155   $hourFormat = "%a at $hourFormat" unless $hourFormat =~ /%[aA]/g;
156   $ago = UnixDate($date, $hourFormat);
157 } else {
158   $hourFormat =~ s/\s*%[aA]\s*//;
159   $ago = UnixDate($date, $hourFormat);
160 }
161 $ago = Delta_Format(DateCalc($date, $now), 0, "%st sec ago")
162   if ($ago =~ /0 min ago/);
163 $feelsLike = $data{current}{windchill}
164   if (not defined $feelsLike) or $feelsLike =~ /^\s*N[\s\/]*A\s*$/i;
165 undef $feelsLike if defined $feelsLike and $feelsLike =~ /^\s*(N[\s\/]*A)?\s*$/i;
166 undef $windGust if defined $windGust   and $windGust =~ /^\s*(N[\s\/]*A)?\s*$/i;
167 undef $windSpeed if defined $windSpeed and $windSpeed =~ /^\s*(N[\s\/]*A)?\s*$/i;
168 undef $weatherConditions
169   if defined $weatherConditions and $weatherConditions =~ /^\s*(N[\s\/]*A|unknown)?\s*$/i;
170
171 $icon = $data{extended}{"icon-0"}
172   if ($icon =~ /unknown/i and $data{extended}{"date-0"} eq UnixDate($now, "%A"));
173
174 my($xpos, $vpos) = (350, $VOFFSET_IMAGE + 40);
175 my $smallFontSize = $FONT_SIZE - 5;
176 $smallFontSize = 7 if $smallFontSize < 7;
177 (defined $ago) ?
178   print "\${alignr}\${font :size=${smallFontSize}px}(as of $ago)\n" :
179    print "\n";
180 print "\${font :size=${FONT_SIZE}px} Current: $temp $degree";
181 print " (feels like: $feelsLike $degree)" if defined $feelsLike;
182 print "\${image $mythIconPath/$icon -p $xpos,$vpos  -s 50x37}"
183   unless $icon =~ /unknown/i;
184 print "\n\${goto 82}Humidity: $humidity\%";
185 print "     Wind: " if defined $windSpeed or defined $windGust;
186 print "$windSpeed kph" if defined $windSpeed;
187 print "  ($windGust kph)" if defined $windGust;
188 print "\n\${goto 82}Conditions: $weatherConditions\n" if defined $weatherConditions;
189 print "\n";
190 ($xpos, $vpos) = ($FONT_SIZE * (5 + $data{forecast}{maxLength}),
191                   $VOFFSET_IMAGE + 78);
192
193 my $cnt = 0;
194 foreach my $ii (qw/0 1 2 3 4 5/) {
195   next if not defined $data{forecast}{"time-${ii}"};
196   $cnt++;
197   my($time, $temp, $pop, $icon) =
198     ($data{forecast}{"time-${ii}"}, $data{forecast}{"temp-${ii}"},
199      $data{forecast}{"pop-${ii}"}, $data{forecast}{"18icon-${ii}"});
200   $pop =~ s/\s+//g;
201   $pop = "  $pop" if length($pop) eq 2;
202   $pop = " $pop" if length($pop) eq 3;
203   print "\${font :size=${FONT_SIZE}px} $time:\${goto 120}$temp $degree \${image $mythIconPath/$icon -p $xpos,$vpos  -s 20x15}     $pop chance\n";
204   $vpos += $FONT_SIZE + 7;
205 }
206 ($xpos, $vpos) = ($FONT_SIZE * 26,
207                     $VOFFSET_IMAGE + 173);
208 foreach my $ii (qw/0 1 2 3 4 5/) {
209   # You can also use "%doneDays" here, as in:
210   #      next if defined $doneDays{$data{extended}{"date-${ii}"}};
211   next if not defined $data{extended}{"date-${ii}"};
212   next if $data{extended}{"date-${ii}"} eq UnixDate($now, "%A");
213   my($day, $high, $low, $icon) =
214     ($data{extended}{"date-${ii}"}, $data{extended}{"high-${ii}"},
215      $data{extended}{"low-${ii}"}, $data{extended}{"icon-${ii}"});
216   print "\${font :size=${FONT_SIZE}px} $day:\${goto 120}High: $high $degree   Low: $low $degree \${image $mythIconPath/$icon -p $xpos,$vpos  -s 20x15}\n";
217   $vpos += $FONT_SIZE + 8;
218 }
219
220 ###############################################################################
221 #
222 # Local variables:
223 # compile-command: "perl -c conky-mythtv-weather-build.plx"
224 # End:
225