Thumbnail: i3wm

Weather with i3status

on under i3wm
10 minute read

If you don’t care how it works, and just want to implement it, here’s the TL;DR.

It’s annyoing to have to check up on the weather once in a while through your phone, browser or sticking your head out of the window. That and because I have too much room to spare in my i3bar, I put it in my i3status. There’s already a good post about it, but it was a little bit more complicated than that and the solution too abridged for me.

They seem to have changed the layout or that person lives somewhere, where it is never sunny, because it didn’t work by just copy and pasting. Also, there are better ways to do the same thing.

Cronjob or how to get the data

How to deal with cronjobs

I’m starting from scratch, because I hate to look up solutions for other problems resulting from a post, because the author assumes that they should be known.

Cronjob is a way to let the machine do routine things in the background. These tasks can be done at reboot, yearly, daily, hourly, minutely and so on. I’m using cronie , but the command crontab -e should work for other cronjob solutions as well. With this command, you get to edit your cronjob file with your favourite/default editor. If you can’t handle that, look at this.

Constructing the cronjob command

Now that the file is open, we have to understand and deal with the cronjob syntax. If you don’t wan’t to understand, look here, which will give you the correct input.

We want an up to date weather information, but 5 minutes is way too often, half an hour would suffice, but let’s do 10 minutes.

Every cronjob beginns with either a keyword or a combination of asterisks. I’m only considering @reboot of the keyword ones, because I want to know the current weather at boot without waiting 10 minutes at most. (I don’t like looking at the value from yesterday.)

So I construct two cronjobs with the following openings:

*/10 * * * * <command>
@reboot <command>

We use */10 because we want it updated every 10 minutes, and not every hour at xx:10, that’s why there is a */.

Fetching the weather from wttr.in

Wttr.in is a nice site, because it’s made for fetching data through curl. This is also where the original guide is wrong. It fetches this whole site and sifts through to get the data. This is a little inconvenient and didn’t work for me because it was sunny. This is a problem, because the ASCII-Art uses a some nasty little characters like / and \ that escapes stuff through the string. Instead of dealing with that, we use the features provided by that site, because we can simply choose a format.

I want to know the weather, the correct emoji and the temperature. I also want to force celsius, represented by that ‘m’. That’s why I fetch the information by using:

curl -s wttr.in/your_city?m\&format="\%C+\%c+\%t"

which gives the nice output of

Partly cloudy ⛅️  -2°C

The format is nice, because you can build it just like you want, even without Emoji if you are not into that. Here’s the repo which also includes examples and explainations of the formats and stuff.

Caching the data

So now we got the source and cronjob, we also have to save it somewhere. That’s the simple part, we just make a cache file in our home folder. If you don’t trust my/your cronjob task to make the file, go ahead and make it with the following name ~/.weather.cache. It’s a dotfile, so I don’t have to look at it every time I open my home folder.

You could still use your crontab -e so let’s get it over with. Put in those two lines or your own variation of it:

*/10 * * * * curl -s wttr.in/your_city?m\&format="\%C+\%c+\%t" > ~/.weather.cache
@reboot curl -s wttr.in/your_city?m\&format="\%C+\%c+\%t" > ~/.weather.cache

So we fetch the information every 10 minutes (meaning :00, :10, :20, :30, …) and put it into the cache file. That’s it, now we have to write our own bash file for dealing with the other i3status stuff and then put it into the i3bar.

Dealing with the data

Let’s make a file in the bin folder in our home folder. touch ~/bin/weather.sh for the lazy. There may be no bin folder in your home, so mkdir bin first of course. Also, make sure your permissions are correct chmod 0755 weather.sh, or it can’t be used by i3bar.

This is the code, we will go through it afterwards:

#!/bin/sh
# shell script to prepend i3status with weather

# Temperature as entries in a list
temps[35]="#FF0000";
temps[34]="#FF1400";
temps[33]="#FF2800";
temps[32]="#FF3C00";
temps[31]="#FF5000";
temps[30]="#FF6400";
temps[29]="#FF7800";
temps[28]="#FF8C00";
temps[27]="#FFA000";
temps[26]="#FFB400";
temps[25]="#FFC800";
temps[24]="#FFDC00";
temps[23]="#FFF000";
temps[22]="#FDFF00";
temps[21]="#B0FF00";
temps[20]="#65FF00";
temps[19]="#3EFF00";
temps[18]="#17FF00";
temps[17]="#00FF10";
temps[16]="#00FF36";
temps[15]="#00FF5C";
temps[14]="#00FF83";
temps[13]="#00FFA8";
temps[12]="#00FFD0";
temps[11]="#00FFF4";
temps[10]="#00E4FF";
temps[9]="#00D4FF";
temps[8]="#00C4FF";
temps[7]="#00B4FF";
temps[6]="#00A4FF";
temps[5]="#0094FF";
temps[4]="#0084FF";
temps[3]="#0074FF";
temps[2]="#0064FF";
temps[1]="#0054FF";
temps[0]="#0044FF";

# This looks ugly, but I have to represent minus values somehow. 
# Hopefully climate change never gets that bad
temps[101]="#0032FF";
temps[102]="#0022FF";
temps[103]="#0012FF";
temps[104]="#0002FF";
temps[105]="#0000FF";
temps[106]="#0100FF";
temps[107]="#0200FF";
temps[108]="#0300FF";
temps[109]="#0400FF";
temps[110]="#0500FF";
temps[111]="#0600FF";


i3status | (read line && echo "$line" && read line && 
    echo "$line" && read line && echo "$line" && while :
do
    read line
    weather=$(head -n 1 ~/.weather.cache)
    color="#FF0000"
    if [ "${weather:0:2}" = "<!" ]; then
        weather="Not Available now"
    elif [ "${weather:0:2}" = "<h" ]; then
        weather="Not Available now"
    elif [ "${weather:0:16}" = "Unknown location" ]; then
        weather="Unknown location"
    else 
        end=${weather: -5}
        temp=${end:0:3}
        if [ "$temp" -lt 0 ]; then
            temp=${temp:1} 
            temp=$(( temp + 100 )) 
        elif [ "$temp" -gt 35 ]; then
            temp=35
            # Send help
        fi

        color="${temps[$temp]}"
    fi
    echo ",[{\"full_text\":\"${weather}\",\"color\":\"$color\" },${line#,\[}" || exit 1
done)

First we can insert every possible color value for each temperature. Depending on your resistance to different temperatures, you can of course change the color codes or skip colors altogether. This may seem a little inconvenient, but it’s far better than the first version.

Next, we gonna pipe the rest of the i3status stuff through, do a while with some weird read and echo line stuff and get to the actual meat of the script. Why do we have to do the read and echo stuff 3 times? I guess it makes the i3status format work. I’m not sure but having more only makes it slower, while having none gets us the JSON output and having less results in an error. I guess it’s just how we need to accept input here.

So we get the weather from the cache file with head, because sometimes there is an error which doesn’t give us our beloved weather information, but instead some html output. It’s something to do with wttr.in sometimes running out of requests or internal server errors. That’s why I’m checking the first two chars: If the string begins with <! or a <h then we are dealing with the beginning of the html-file and show “Not Available now” instead, to prevent errors in the JSON-Reader of i3bar, killing i3bar, fixing the cache and having to restart i3. Sometimes, it doesn’t recognize my location, probably another server-side error, but at least it doesn’t answer with an html-file.

I want to have a different color depending on the temperature, that’s why I get and cut out the temperature reading from the cache and throw it in a temp variable. Because data structures like arrays don’t usually allow for minus values, I have to catch those, delete the - and get the correct color by adding 100. Could have made another one just for the minus temps, but I’m not expecting temperatures over 100°C anytime soon.

At last, I’m echoing the weather and it’s color.

Connecting the cables

Now we have to make i3 use this nice script. For this, go to your i3 config file (for me it’s at ~/.i3/config).

Rather far down we have the definition of the i3bar, something like this here:

bar {
    status_command i3status
    ...

We have put our script in between, so that we can change what we see at the bar. As we have already seen in the script, we use i3status, so let’s use our script instead (which contains i3status).

bar {
    status_command ~/bin/weather.sh
    ...

Almost done! We have to make sure, that i3status uses the JSON output. For this, we have to change ~/.i3status.conf. If you are a good person, unlike me, you would copy the one at /etc/i3status.conf and put it into your home folder. I didn’t do that and changed the /etc/i3status.conf. In general, it’s a nice file to play with anyway, so take a look what you can change there. Other than that, make sure these three lines are set:

general {
        colors = true
        interval = 5
        output_format = "i3bar"
        ...
}

And that’s it. I hope I didn’t forget anything, so it should work just like that.

This is what it should look like: Screenshot

For the lazy, here comes the short version.

TL;DR

Set off this command in your shell:

[~]$ crontab -e

Copy and paste this, substituting your_city with, you guessed it, your city:

*/10 * * * * curl -s wttr.in/your_city?m\&format="\%C+\%c+\%t" > ~/.weather.cache
@reboot curl -s wttr.in/your_city?m\&format="\%C+\%c+\%t" > ~/.weather.cache

Make a bin folder in your home, if not already existing, put your weather.sh file in there. Copy and paste this code into this file:

#!/bin/sh
# shell script to prepend i3status with weather

# Temperature as entries in a list
temps[35]="#FF0000";
temps[34]="#FF1400";
temps[33]="#FF2800";
temps[32]="#FF3C00";
temps[31]="#FF5000";
temps[30]="#FF6400";
temps[29]="#FF7800";
temps[28]="#FF8C00";
temps[27]="#FFA000";
temps[26]="#FFB400";
temps[25]="#FFC800";
temps[24]="#FFDC00";
temps[23]="#FFF000";
temps[22]="#FDFF00";
temps[21]="#B0FF00";
temps[20]="#65FF00";
temps[19]="#3EFF00";
temps[18]="#17FF00";
temps[17]="#00FF10";
temps[16]="#00FF36";
temps[15]="#00FF5C";
temps[14]="#00FF83";
temps[13]="#00FFA8";
temps[12]="#00FFD0";
temps[11]="#00FFF4";
temps[10]="#00E4FF";
temps[9]="#00D4FF";
temps[8]="#00C4FF";
temps[7]="#00B4FF";
temps[6]="#00A4FF";
temps[5]="#0094FF";
temps[4]="#0084FF";
temps[3]="#0074FF";
temps[2]="#0064FF";
temps[1]="#0054FF";
temps[0]="#0044FF";

# This looks ugly, but I have to represent minus values somehow. 
# Hopefully climate change never gets that bad
temps[101]="#0032FF";
temps[102]="#0022FF";
temps[103]="#0012FF";
temps[104]="#0002FF";
temps[105]="#0000FF";
temps[106]="#0100FF";
temps[107]="#0200FF";
temps[108]="#0300FF";
temps[109]="#0400FF";
temps[110]="#0500FF";
temps[111]="#0600FF";


i3status | (read line && echo "$line" && read line && 
    echo "$line" && read line && echo "$line" && while :
do
    read line
    weather=$(head -n 1 ~/.weather.cache)
    color="#FF0000"
    if [ "${weather:0:2}" = "<!" ]; then
        weather="Not Available now"
    elif [ "${weather:0:2}" = "<h" ]; then
        weather="Not Available now"
    elif [ "${weather:0:16}" = "Unknown location" ]; then
        weather="Unknown location"
    else 
        end=${weather: -5}
        temp=${end:0:3}
        if [ "$temp" -lt 0 ]; then
            temp=${temp:1} 
            temp=$(( temp + 100 )) 
        elif [ "$temp" -gt 35 ]; then
            temp=35
            # Send help
        fi

        color="${temps[$temp]}"
    fi
    echo ",[{\"full_text\":\"${weather}\",\"color\":\"$color\" },${line#,\[}" || exit 1
done)

Set this line in your i3 config file (~/i3/config):

bar {
    status_command ~/bin/weather.sh
    ...

Make sure, your i3status(~/.i3status.conf or /etc/i3status.conf) allows JSON and colors:

general {
        colors = true
        interval = 5
        output_format = "i3bar"
        ...
}
i3wm, desktop environment, linux, i3status, weather, wttr.in