Script for weather reporting in Waybar
While I was living in Argentina, we (my family) found ourselves checking for weather forecasts almost constantly — weather there can be quite unexpected, much more so that here in Mexico. So it took me a bit of tinkering to come up with a couple of simple scripts to show the weather forecast as part of my Waybar setup. I haven’t cared to share with anybody, as I believe them to be quite trivial and quite dirty.
But today, Víctor was asking for some slightly-related things, so here I go. Please do remember I warned: Dirty.
I am using OpenWeather’s open
API. I had to register to get an APPID, and it
allows me for up to 1,000 API calls per day, more than plenty for my uses, even
if I am logged in at my desktops at three different computers (not an uncommon
situation). Having that, I set up a file named /etc/get_weather/
, that
currently reads:
# Home, Mexico City
LAT=19.3364
LONG=-99.1819
# # Home, Paraná, Argentina
# LAT=-31.7208
# LONG=-60.5317
# # PKNU, Busan, South Korea
# LAT=35.1339
#LONG=129.1055
APPID=SomeLongRandomStringIAmNotSharing
Then, I have a simple script, /usr/local/bin/get_weather
, that fetches the
current weather and the forecast, and stores them as /run/weather.json
and
/run/forecast.json
:
#!/usr/bin/bash
CONF_FILE=/etc/get_weather
if [ -e "$CONF_FILE" ]; then
. "$CONF_FILE"
else
echo "Configuration file $CONF_FILE not found"
exit 1
fi
if [ -z "$LAT" -o -z "$LONG" -o -z "$APPID" ]; then
echo "Configuration file must declare latitude (LAT), longitude (LONG) "
echo "and app ID (APPID)."
exit 1
fi
CURRENT=/run/weather.json
FORECAST=/run/forecast.json
wget -q "https://api.openweathermap.org/data/2.5/weather?lat=${LAT}&lon=${LONG}&units=metric&appid=${APPID}" -O "${CURRENT}"
wget -q "https://api.openweathermap.org/data/2.5/forecast?lat=${LAT}&lon=${LONG}&units=metric&appid=${APPID}" -O "${FORECAST}"
This script is called by the corresponding systemd service unit, found at
/etc/systemd/system/get_weather.service
:
[Unit]
Description=Get the current weather
[Service]
Type=oneshot
ExecStart=/usr/local/bin/get_weather
And it is run every 15 minutes via the following systemd timer unit,
/etc/systemd/system/get_weather.timer
:
[Unit]
Description=Get the current weather every 15 minutes
[Timer]
OnCalendar=*:00/15:00
Unit=get_weather.service
[Install]
WantedBy=multi-user.target
(yes, it runs even if I’m not logged in, wasting some of my free API calls… but within reason)
Then, I declare a "custom/weather"
module in the desired position of my
~/.config/waybar/waybar.config
, and define it as:
"custom/weather": {
"exec": "while true;do /home/gwolf/bin/parse_weather.rb;sleep 10; done",
"return-type": "json",
},
This script basically morphs a generic weather JSON description into another set of JSON bits that display my weather in the way I prefer to have it displayed as:
#!/usr/bin/ruby
require 'json'
Sources = {:weather => '/run/weather.json',
:forecast => '/run/forecast.json'
}
Icons = {'01d' => '🌞', # d → day
'01n' => '🌃', # n → night
'02d' => '🌤️',
'02n' => '🌥',
'03d' => '☁️',
'03n' => '🌤',
'04d' => '☁️',
'04n' => '🌤',
'09d' => '🌧️',
'10n' => '🌧 ',
'10d' => '🌦️',
'13d' => '❄️',
'50d' => '🌫️'
}
ret = {'text': nil, 'tooltip': nil, 'class': 'weather', 'percentage': 100}
# Current weather report: Main text of the module
begin
weather = JSON.parse(open(Sources[:weather],'r').read)
loc_name = weather['name']
icon = Icons[weather['weather'][0]['icon']] || '?' + f['weather'][0]['icon'] + f['weather'][0]['main']
temp = weather['main']['temp']
sens = weather['main']['feels_like']
hum = weather['main']['humidity']
wind_vel = weather['wind']['speed']
wind_dir = weather['wind']['deg']
portions = {}
portions[:loc] = loc_name
portions[:temp] = '%s 🌡%2.2f°C (%2.2f)' % [icon, temp, sens]
portions[:hum] = '💧 %2d%%' % hum
portions[:wind] = '🌬%2.2fm/s %d°' % [wind_vel, wind_dir]
ret['text'] = [:loc, :temp, :hum, :wind].map {|p| portions[p]}.join(' ')
rescue => err
ret['text'] = 'Could not process weather file (%s ⇒ %s: %s)' % [Sources[:weather], err.class, err.to_s]
end
# Weather prevision for the following hours/days
begin
cast = []
forecast = JSON.parse(open(Sources[:forecast], 'r').read)
min = ''
max = ''
day=Time.now.strftime('%Y.%m.%d')
by_day = {}
forecast['list'].each_with_index do |f,i|
by_day[day] ||= []
time = Time.at(f['dt'])
time_lbl = '%02d:%02d' % [time.hour, time.min]
icon = Icons[f['weather'][0]['icon']] || '?' + f['weather'][0]['icon'] + f['weather'][0]['main']
by_day[day] << f['main']['temp']
if time.hour == 0
min = '%2.2f' % by_day[day].min
max = '%2.2f' % by_day[day].max
cast << ' ↑ min: <b>%s°C</b> max: <b>%s°C</b>' % [min, max]
day = time.strftime('%Y.%m.%d')
cast << ' ┍━━━━━┫ <b>%04d.%02d.%02d</b> ┠━━━━━┑' %
[time.year, time.month, time.day]
end
cast << '%s | %2.2f°C | 🌢%2d%% | %s %s' % [time_lbl,
f['main']['temp'],
f['main']['humidity'],
icon,
f['weather'][0]['description']
]
end
cast << ' ↑ min: <b>%s</b>°C max: <b>%s°C</b>' % [min, max]
ret['tooltip'] = cast.join("\n")
rescue => err
ret['tooltip'] = 'Could not process forecast file (%s ⇒ %s)' % [Sources[:forecast], err.class, err.to_s]
end
# Print out the result for Waybar to process
puts ret.to_json
The end result? Nothing too stunning, but definitively something I find useful and even nicely laid out:
Do note that it seems OpenWeather will return the name of the closest available meteorology station with (most?) recent data — for my home, I often get Ciudad Universitaria, but sometimes Coyoacán or even San Ángel Inn.