Handle different wind field.
[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 use File::Spec;
35
36 my $DIR = File::Spec->catdir("$ENV{HOME}", 'tmp', '.conky-mythtv-weather');
37 my $VOFFSET_FILE = File::Spec->catfile($DIR, 'conky-weather-voffset-last');
38
39 chdir($DIR) or die "unable to go to $DIR";
40
41 if (@ARGV != 6 and @ARGV != 7) {
42   print STDERR "usage: $0 /path/to/mythtv/git/checkout <units> <location> <text_voffset> <img_voffset> <fontsize_pixels> [hour-format]\n";
43   exit 1;
44 }
45 my($MYTH_PATH, $UNITS, $LOCATION, $VOFFSET_TEXT, $VOFFSET_IMAGE, $FONT_SIZE, $HOUR_FORMAT) = @ARGV;
46 $HOUR_FORMAT = "%a %H:%M" unless defined $HOUR_FORMAT;
47 my $TEXT_LINE_OFFSET_VPOS_AMOUNT = 1.59;
48 my $VOFFSET_TOP_DEFAULT_OFFSET = 11;
49
50 if ($VOFFSET_IMAGE eq 'READ') {
51   open(VOFFSET, "<", $VOFFSET_FILE) or die "unable to read $VOFFSET_FILE: $!";
52   while (my $line = <VOFFSET>) {
53     chomp $line;
54     $VOFFSET_IMAGE = $line if $line =~ /^\s*[\d\.]+\s*$/;
55   }
56   close VOFFSET;
57   die "unable to read voffset from $VOFFSET_FILE: $!"
58     if ($VOFFSET_IMAGE eq 'READ');
59 }
60 $VOFFSET_IMAGE += $VOFFSET_TOP_DEFAULT_OFFSET;
61
62 my $degree;
63 if ($UNITS eq "SI") {
64   $degree = encode('utf8', "°C");
65 } elsif ($UNITS eq 'ENG') {
66   $degree = encode('utf8', "°F");
67 } else {
68   die "invalid units, $UNITS";
69 }
70
71 my($mythIconPath) = $MYTH_PATH .  "/mythplugins/mythweather/theme/default/icons";
72 my(%commands) = ('forecast' => $MYTH_PATH .
73                  "/mythplugins/mythweather/mythweather/scripts/us_nws/ndfd18.pl",
74                  'extended' => $MYTH_PATH .
75                  "/mythplugins/mythweather/mythweather/scripts/us_nws/ndfd.pl",
76                  'current' => $MYTH_PATH .
77                  "/mythplugins/mythweather/mythweather/scripts/us_nws/nwsxml.pl",
78                 'accuweather' => $MYTH_PATH .
79                 "/mythplugins/mythweather/mythweather/scripts/accuweather/accuweather.pl");
80
81 my %data;
82
83 my($location1) = $LOCATION;
84 my($location2) = $LOCATION;
85 if ($LOCATION =~ /^([^|]+)\|([^|]+)/) {
86   ($location1, $location2) = ($1, $2);
87 }
88 if ($location1 eq "FORCE_ACCUWEATHER") {
89   foreach my $key (qw/forecast extended current/) { delete $commands{$key}; }
90 }
91 foreach my $type (keys %commands) {
92   my $location = $location1;
93   $location = $location2 if $type eq "accuweather";
94   open(DATA, "-|", $commands{$type}, '-u', $UNITS, $location)
95     or die "unable to run: $commands{$type} -u $UNITS $LOCATION: $!";
96
97   while (my $line = <DATA>) {
98     die "bad line output in data: $line"
99       unless $line =~ /^\s*(\S+)\s*:\s*:\s*(.*)$/;
100     $data{$type}{$1} = $2;
101   }
102   close DATA;
103   die "error($?) running: $commands{$type} -u $UNITS $LOCATION: $!"
104     unless $? == 0;
105 }
106 if (not defined $data{current}{observation_time}) {
107   foreach my $key (%{$data{accuweather}}) {
108     $data{current}{$key} = $data{accuweather}{$key};
109   }
110 }
111 if (not defined $data{forecast}{updatetime}
112     and defined $data{accuweather}{'time-1'}) {
113   foreach my $key (%{$data{accuweather}}) {
114     $data{forecast}{$key} = $data{accuweather}{$key};
115   }
116 }
117 if (not defined $data{extended}{updatetime}) {
118   foreach my $key (%{$data{accuweather}}) {
119     $data{extended}{$key} = $data{accuweather}{$key};
120   }
121 }
122
123 my $updateTime = $data{forecast}{updatetime};
124 $updateTime = $data{extended}{updatetime} if not defined $updateTime;
125
126 $updateTime =~ s/\s*(?:Last\s*)?Updated\s*(?:on|:)?\s*//;
127 my $now =  ParseDate("now");
128 $updateTime = ParseDate($updateTime);
129 my $x = Delta_Format(DateCalc($updateTime, $now), 0, "%mt minutes ago");
130
131 $data{forecast}{updatetime} = $x if defined $x;
132 $data{forecast}{updatetime} = "as of $data{forecast}{updatetime}";
133 $data{forecast}{"maxLength"} = 0;
134 my %doneDays;
135 foreach my $ii (qw/0 1 2 3 4 5/) {
136   next if not defined $data{forecast}{"time-${ii}"};
137   my $time = ParseDate($data{forecast}{"time-${ii}"});
138   next if not defined $time;
139   if (defined $time) {
140     $time = DateCalc($time, "+ 1 day") if ($time lt $updateTime);
141     if ($time lt $now) {
142       delete $data{forecast}{"time-${ii}"};
143       next;
144     }
145     my $day = UnixDate($time, '%A');
146     $data{forecast}{"time-${ii}"} = UnixDate($time, $HOUR_FORMAT);
147     my $ll = length($data{forecast}{"time-${ii}"});
148     $data{forecast}{"maxLength"} = $ll
149       unless $data{forecast}{"maxLength"} > $ll;
150     $doneDays{$day} = 'forecast';
151   }
152 }
153 my $f = $FONT_SIZE + 5;
154
155 my($xpos, $vpos) = (340, $VOFFSET_IMAGE);
156
157 my $place = $data{current}{'cclocation'};
158 $place .= " $location2" if ($location1 eq "FORCE_ACCUWEATHER");
159
160 print '${voffset ', $VOFFSET_TEXT , '} ${font :size=', $f, '}${alignc}Weather:${font}', " $place\n";
161
162 $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $f);
163
164 if (not defined $data{current}{observation_time_rfc822}) {
165   $data{current}{observation_time_rfc822} = $data{current}{observation_time};
166   $data{current}{observation_time_rfc822} =~ s/^\s*(?:Observation\s*of\s*:?|Last\s*Updated\s*(?:on)?)\s*//;
167 }
168 my($temp, $feelsLike, $humidity, $windSpeed, $windGust, $icon, $datetime, $weatherConditions) =
169   ($data{current}{temp}, $data{current}{heat_index},
170    $data{current}{relative_humidity}, $data{current}{wind_speed},
171    $data{current}{wind_gust}, $data{current}{weather_icon},
172    $data{current}{observation_time_rfc822}, $data{current}{weather});
173
174 if (defined $data{current}{wind_spdgst} and
175     (not defined $data{current}{wind_speed})) {
176   $data{current}{wind_spdgst} =~ /^\s*([\d\.]+)\s*\(?\s*([\d\.]+)\s*\)?\s*$/;
177   ($data{current}{wind_speed}, $data{current}{wind_gust}) = ($1, $2);
178 }
179 my $date = ParseDate($datetime);
180
181 my $howOld = DateCalc($date, $now);
182 my $ago = Delta_Format($howOld, 0, "%mt min ago");
183 my $hourFormat = $HOUR_FORMAT;
184 if ($howOld ge DateCalc($now, "+ 1 day")) {
185   $ago =  Delta_Format($howOld, 0, "\${color5}%dt day(s) ago\${color}");
186 } elsif (UnixDate($date, "%Y-%m-%d") ne UnixDate($now, "%Y-%m-%d")) {
187   $hourFormat = "%a at $hourFormat" unless $hourFormat =~ /%[aA]/g;
188   $ago = UnixDate($date, $hourFormat);
189 } else {
190   $hourFormat =~ s/\s*%[aA]\s*//;
191   $ago = UnixDate($date, $hourFormat);
192 }
193 $ago = Delta_Format(DateCalc($date, $now), 0, "%st sec ago")
194   if ($ago =~ /0 min ago/);
195 $feelsLike = $data{current}{windchill}
196   if (not defined $feelsLike) or $feelsLike =~ /^\s*N[\s\/]*A\s*$/i;
197 undef $feelsLike if defined $feelsLike and $feelsLike =~ /^\s*(N[\s\/]*A)?\s*$/i;
198 undef $windGust if defined $windGust   and $windGust =~ /^\s*(N[\s\/]*A)?\s*$/i;
199 undef $windSpeed if defined $windSpeed and $windSpeed =~ /^\s*(N[\s\/]*A)?\s*$/i;
200 undef $weatherConditions
201   if defined $weatherConditions and $weatherConditions =~ /^\s*(N[\s\/]*A|unknown)?\s*$/i;
202
203 $icon = $data{extended}{"icon-0"}
204   if ($icon =~ /unknown/i and $data{extended}{"date-0"} eq UnixDate($now, "%A"));
205
206 my $smallFontSize = $FONT_SIZE - 5;
207 $smallFontSize = 7 if $smallFontSize < 7;
208 (defined $ago) ?
209   print "\${alignr}\${font :size=${smallFontSize}px}(as of $ago)\n" :
210    print "\n";
211 $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $smallFontSize);
212 print "\${font :size=${FONT_SIZE}px} Current: $temp $degree";
213 print " (feels like: $feelsLike $degree)" if defined $feelsLike;
214 print "\${image $mythIconPath/$icon -p $xpos,$vpos  -s 50x37}"
215   unless $icon =~ /unknown/i;
216 $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
217 print "\n\${goto 82}Humidity: $humidity\%";
218 print "     Wind: " if defined $windSpeed or defined $windGust;
219 print "$windSpeed kph" if defined $windSpeed;
220 print "  ($windGust kph)" if defined $windGust;
221 $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
222 if (defined $weatherConditions) {
223   print "\n\${goto 82}Conditions: $weatherConditions\n";
224     $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
225 }
226 $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
227 print "\n";
228
229 $xpos = $FONT_SIZE * (4 + $data{forecast}{maxLength});
230
231 foreach my $ii (qw/0 1 2 3 4 5/) {
232   next if not defined $data{forecast}{"time-${ii}"};
233   my($time, $temp, $pop, $icon) =
234     ($data{forecast}{"time-${ii}"}, $data{forecast}{"temp-${ii}"},
235      $data{forecast}{"pop-${ii}"}, $data{forecast}{"18icon-${ii}"});
236   $pop =~ s/\s+//g;
237   $pop = "  $pop" if length($pop) eq 2;
238   $pop = " $pop" if length($pop) eq 3;
239   print "\${font :size=${FONT_SIZE}px} $time:\${goto 120}$temp $degree \${image $mythIconPath/$icon -p $xpos,$vpos  -s 20x15}     $pop chance\n";
240   $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
241 }
242 $xpos = $FONT_SIZE * 26;
243
244 foreach my $ii (qw/0 1 2 3 4 5/) {
245   # You can also use "%doneDays" here, as in:
246   #      next if defined $doneDays{$data{extended}{"date-${ii}"}};
247   next if not defined $data{extended}{"date-${ii}"};
248   next if $data{extended}{"date-${ii}"} eq UnixDate($now, "%A");
249   my($day, $high, $low, $icon) =
250     ($data{extended}{"date-${ii}"}, $data{extended}{"high-${ii}"},
251      $data{extended}{"low-${ii}"}, $data{extended}{"icon-${ii}"});
252   print "\${font :size=${FONT_SIZE}px} $day:\${goto 120}High: $high $degree   Low: $low $degree \${image $mythIconPath/$icon -p $xpos,$vpos  -s 20x15}\n";
253   $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
254 }
255 print "\$hr\n";
256 $vpos += ($TEXT_LINE_OFFSET_VPOS_AMOUNT * $FONT_SIZE);
257 $vpos -= $VOFFSET_TOP_DEFAULT_OFFSET;
258 open(VOFFSET, ">", $VOFFSET_FILE);
259 print VOFFSET "$vpos\n";
260 close VOFFSET;
261 ###############################################################################
262 #
263 # Local variables:
264 # compile-command: "perl -c conky-mythtv-weather-build.plx"
265 # End:
266