Conky and the Weatherman

July 13, 2024

Conky - is a light-weight system monitor for Unix like systems capable of showing a wide variety of system information such as disk, memory, and network utilization, process information, and information from external programs such as popular music players, and IMAP/POP email stats. It is also capable of running external programs or scripts to collect and display other events and information of interest.

It is this last capability - displaying data and events from external sources that will be useful to us in collecting and displaying weather data. Note that Conky has a lot of options, all of which are described in their extensive documentation and in their manual pages. We will touch on several capabilities, focusing on getting and displaying weather data. Getting it to look exactly how you want it to look takes some trial and error so get Conky installed on your system and test out some simple configuation elements first.

Note - we will be using weather information from the US National Weather Service which covers a lot of area, but may not provide weather information for a location you are interested in. If this is the case, the same types of procedures will be of general applicability to other weather data providers in Europe, Africa, Asia, or elsewhere.

Basic Setup

To make this as simple as possible, we will utilize a minimal Conky configuration file. Conky can generate it’s own basic confiration file with the following:

$ conky -C 

Redirect and save the output to a file and use the -c option (lowercase ‘c’) to specify the new configuration file:

$ conky -c ./conky_tmp.conf

You should see something like Figure 1:

Figure 1: Conky Default Setup on FreeBSD Host

Figure 1: Conky Default Setup on FreeBSD Host

This shows a basic Conky instance with system utilization, file system and networking, and a list of processes using system resources.

NOTE: If you don’t see anything, an existing window may be covering the Conky instance drawn on the root window. Move all existing windows away from the upper left corner of your screen, and it should be there. If not, check the website for installation procedures and common errors.

As usual when beginning something new, save a backup of your basic configuration.

Since the configuration file is just text, let’s strip out stuff we don’t need for now. The configuration has a global area at the top, bounded by “conky.config = { … }” braces, then the local variables bounded by “conky.text = [[ … ]]” brackets.

Let’s strip the variables portion down until we have just the following:

Figure 2: Stripped Conky Config

Figure 2: Stripped Conky Config

It’s important to leave the 15 or so blank lines between the last variable and the end marker ‘]]’. This provides the “canvas” on which we will add weather data.

Finding Your Location

Most of the NWS weather Application Programming Interfaces (API) need location information, either latitude and longitude or NWS grid locations, which we will look for below.

To get your latitude and longitude you can get the information from several sources - your smart phone, Google Maps, Open Street Maps, MapQuest, or various websites such as www.latlong.net

Try this website: https://www.latlong.net/convert-address-to-lat-long.html. Enter your information and get your latitude and longitde. My location, near Durham, North Carolina, is approximately 35.9460, -78.9703. We will use these coordinates below. Note that the NWS software only utilizes 4 digits of precision for coordinates.

Getting Weather Data

There are many websites that show US weather data and forecasts, but by and large they all get their data from the same source - US the National Weather Service (NWS) - www.weather.gov, which is itself a division of the US National Oceanic and Atmospheric Administration (NOAA) - www.noaa.gov.

The NWS site is quite large and has a vast amount of very interesting information. For our purposes we need to know the following:

  • Our nearest NWS Weather Forecasting Office (WFO). You can see a map of all the WFOs and the area they cover on this page: https://www.weather.gov/srh/nwsoffices If you mouse over the area you want, you will see the URL change at the bottom in your browser. For example, hovering over the “Norman, OK” text, the URL is “www.weather.gov/oun" The “oun” part is the abbreviation for the WFO for that area, and is usually used capitilized - “OUN”. Clicking on that brings up the web page for the NWS Norman, Oklahoma office.

  • We also want our gridX,Y numbers. The NWS bases its forecasts on a grid surrounding each WFO. Each local grid subdivision is approximately 2.1 kilometers square - small enough to provide an accurate forecast for a specific location. Each WFO has their own grid and has assigned gridX and gridY numbers to each grid subdivision. The grid numbers are returned in an API call as shown below.

  • Next we want to look at the basics of getting weather data from the NWS web APIs. These are available online here: https://www.weather.gov/documentation/services-web-api#/

Figure 3: US NWS API Web Page

Figure 3: US NWS API Web Page

Read the “Overview” and “Examples” tabs, then go to the “Specification” tab and look for the “/points/{point}” API entry.

Figure 4: US NWS “points” API

Figure 4: US NWS “points” API

Clicking on the entry will bring up the usage form. You can see the description and examples in the accompanying text. To try it out directly, click the “Try it out” button, enter in your latitude and longiude (lat/lon) data (no space) and click “Execute”. The API page shows examples for how to retrieve this information via curl, and by web request. We will make use of these in examples below.

The “Response body” section shows the output of the executed command, which is formatted as a JSON dataset. JSON can be daunting in its own right, but we won’t get too deep. We will use the jq(1) command to retrieve the data we need.

Let’s try our first attempt to get the local forecast.

Embed the geo location information above into the following NWS website:

https://www.weather.gov/documentation/services-web-api#/default/point

as described above and click “Execute”.

Figure 5: Executing US NWS “points” API

Figure 5: Executing US NWS “points” API

The API response is shown in the “Response body” box lower down.

Figure 6: Results  of US NWS “points” API

Figure 6: Results of US NWS “points” API

Note the following:

  • The “properties” section contains all the most useful data:

    • the gridID which is the ID of the WFO - RAH
    • the gridX and gridY numbers - 62 and 63 We will use these in later requests.
    • the external URL endpoints for more detailed information: “forecast”, “forecastHourly”, “forecastGridData”, and “observationStations”

If you are unfamiliar with JSON and how to extract data with the jq(1) command, use the curl(1) method shown on the API page and save to a local file as in this short script:

#!/bin/sh 
export LATVAR=35.9460
export LONVAR=-78.9703
export OUTDIR=.

$ curl -s "https://api.weather.gov/points/${LATVAR}%2C${LONVAR}" > ${OUTDIR}/points.json

If you examine the resulting output, you may see a 301 status and this message:

{
  "correlationId": "f9536c3",
  "title": "Adjusting Trailing Zeros Of Point Coordinate",
  "type": "https://api.weather.gov/problems/AdjustTrailingZeroRedundancy",
  "status": 301,
  "detail": "The coordinates cannot have trailing zeros in the decimal digit.
  The location attribute contains your request with the redundancy removed.
  If your client supports it, you will be redirected.",
  "instance": "https://api.weather.gov/requests/f9536c3"
}

Due to a quirk when using curl(1) with the NWS software, your geo coordinates must be only 4 decimal points long and must not end with a zero digit. In this example, the latitude value, 35.9460, needs to be changed to 35.9461 and the API re-run. Code to automatically fix this issue will be described in a follow-up article.

Once the command is successfully completed, practice extracting data using these jq(1) constructs:

$ cat ${OUTDIR}/points.json | jq                        # show everything


$ cat ${OUTDIR}/points.json | jq '.properties.gridID'   # extract the gridID


$ cat ${OUTDIR}/points.json | jq '.properties.gridX'    # extract gridX


$ cat ${OUTDIR}/points.json | jq '.properties.gridY'    # extract gridY


$ cat ${OUTDIR}/points.json | jq '.properties.forecast'    # extract forecast URL


$ cat ${OUTDIR}/points.json | jq '.properties.radarStation'    # extract radarStation ID

We are now in a position to get an actual forecast using curl(1) and jq(1) and then extract the shortForecast, and detailedForecast data elements. Try it out using this script named weather.sh:


#!/bin/sh

# weather.sh -- script to pull NWS weather data for a single location
#
# Can be used standalone or used with Conky configuration file as
# described in this techbits article.
#
# Code is in the public domain as of
# Wed Jul 24 14:32:54 EDT 2024
#
# Author Jim Brown, jpb@jimby.name
#

export LATVAR=35.9461
export LONVAR=-78.9703
export OUTDIR=.

echo 
echo "RESULTS: [${LATVAR}] [${LONVAR}] [${OUTDIR}]"

# Use the NWS /points API to  get the gridId, gridX, gridY values and save to temporary file points.json.
curl -s "https://api.weather.gov/points/${LATVAR}%2C${LONVAR}" > ${OUTDIR}/points.json

echo "Getting gridId, gridX, gridY, forecast, and radarStation"
# Get the gridId, gridX, gridY, and the forecast URL values from the properties list. Also pick up the radar station for later.
GRIDID=`cat ${OUTDIR}/points.json | jq -r '.properties.gridId'`
GRIDX=`cat ${OUTDIR}/points.json | jq -r '.properties.gridX'`
GRIDY=`cat ${OUTDIR}/points.json | jq -r '.properties.gridY'`
FORECASTURL=`cat ${OUTDIR}/points.json | jq -r '.properties.forecast'`
RADARSTATION=`cat ${OUTDIR}/points.json | jq -r '.properties.radarStation'`

echo "[$GRIDID] [$GRIDX] [$GRIDY] [$FORECASTURL] [$RADARSTATION]"

# Finally, get the forecast with the NWS gridpoints API.


echo "Getting forecast"
curl -s  "https://api.weather.gov/gridpoints/${GRIDID}/${GRIDX},${GRIDY}/forecast" -o ${OUTDIR}/forecast.json

echo "Getting the small NWS icon"
# Note: You must use the -r option on jq(1) or curl will fail when used this way.
export NWSICON=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].icon' | sed s/medium/small/`
curl -s  ${NWSICON} -o ${OUTDIR}/icon.png


# Get the loop map for later display.
echo "Getting loop map"
curl -s  "https://radar.weather.gov/ridge/standard/${RADARSTATION}_loop.gif" -o ${OUTDIR}/map_loop.gif

# OK, let's see the detailed forecast.

echo
echo "The forecast is:"

cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].name,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].detailedForecast,"\n"'

echo
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].name,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].detailedForecast,"\n"'

echo
echo

If you examine the forecast.json file:


$ cat forecast.json | jq

The “forecast” enpoint returns a JSON result that includes an array named “.periods[]”. These are 12 hour blocks of forecast info for the gridXY supplied. Period number 1 is for the current 12-hour period, period 2, is for the next 12-hour period, etc. Remember though that JSON uses zero-based array numbering, so the most current 12-hour forecast is always at:

$ cat ${OUTDIR}/forecast.json | jq '.periods[0]'

This endpoint includes several other data elements:

number, name, startTime, endTime, isDaytime, temperature, temperatureUnit, temperatureTrend, probabilityOfPrecipitation, windSpeed, windDirection, icon, shortForecast, and detailedForecast

Each can be extracted with code similar to:

$ cat ${OUTDIR}/forecast.json | jq '.periods[0].temperature'

The icon endpoint points to a URL with the NWS icon in either “small”, “medium”, or “large” size. The icon is not a weather map, it is just a small picture of idealized current conditions. See the row of icons on the map click pages of the NWS website such as: https://forecast.weather.gov/MapClick.php?textField1=35.79&textField2=-78.65 These are the icon file types:

  • small: PNG image data, 56 x 56, 8-bit/color RGB, non-interlaced
  • medium: PNG image data, 86 x 86, 8-bit/color RGB, non-interlaced
  • large: PNG image data, 134 x 134, 8-bit/color RGB, non-interlaced

There is an interesting discussion on these new (2024) graphics used by the NWS. https://news.ucar.edu/16094/picturing-forecast-national-weather-service-graphics-developed-ncar-research and why they were developed the way they are.

The verbiage for the forecast over the next 12 hours in both short and long variety is found in the forecast.json file:

 "shortForecast": "Mostly Clear then Slight Chance Showers And Thunderstorms",
 "detailedForecast": "A slight chance of showers and thunderstorms after 2am.
                      Mostly clear, with a low around 69. South wind around 5 mph."

These can be extracted separately:

$ cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].shortForecast'

and

$ cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].detailedForecast'

Putting Conky Together

We can now craft our Conky configuration file to use some of this data. Using Conky configuration file variable properties we can now show something like this:

Figure 7: Conky with our first configuration attempt

Figure 7: Conky with our first configuration attempt

Notice that the image is overwriting the text. We can reposition the image with suitable values in the image variable tag which is currently set at:

${image ~/src/conky_nws/.cache/icon.png -p 25,200 -n}

Before repositioning the image however, we should also consider the text for the detailedForecast. It turns out that Conky does not wordwrap text, so we will have to do that ourselves. Here’s code for wrapping the detailedForecast using fmt(1), and shown with Unix line continuation (’') which is not needed in the actual Conky configuration file:

Detailed Forecast: ${execi 30 cat ~/src/conky_nws/.cache/forecast.json | \
 jq -j  '.properties.periods[0].detailedForecast' | fmt -w 40}

Since we don’t know ahead of time how much text the detailedForecast will have, let’s position the image to live in some blank space at the top of the Conky window instead of at the bottom. Here’s the entire Conky variables section with these changes:

-- Variables: https://conky.cc/variables
conky.text = [[
${color grey}Uptime:$color $uptime
$hr
${execi 60 ~/src/conky_nws/weather.sh > /dev/null 2>&1}

Temp: ${execi 30 cat ~/src/conky_nws/.cache/forecast.json | jq -rj  '.properties.periods[0].temperature,"°",.properties.periods[0].temperatureUnit'}

Short Forecast: ${execi 30 cat ~/src/conky_nws/.cache/forecast.json | jq -r  '.properties.periods[0].shortForecast'}

Prob of Precip: ${execi 30  cat ~/src/conky_nws/.cache/forecast.json | jq -rj '.properties.periods[0].probabilityOfPrecipitation.value,"%"'}

Wind: ${execi 30 cat ~/src/conky_nws/.cache/forecast.json | jq -rj '.properties.periods[0].windSpeed," from ",.properties.periods[0].windDirection'}

Detailed Forecast: ${execi 30 cat ~/src/conky_nws/.cache/forecast.json | jq -r  '.properties.periods[0].detailedForecast' | fmt -w 40}

${image ~/src/conky_nws/.cache/icon.png -p 150,40 -n}


]] 

which produces:

Figure 8: Conky with our second configuration attempt

Figure 8: Conky with our second configuration attempt

We have reached a point where the only real work left is positioning all the data bits where you want them. Have a look at man 1 conky for details.

Conclusion

In this techbits article, we’ve successfully set up Conky to display weather data from the US National Weather Service.

A future article will include how to process alerts, and show those moving weather maps. Stay tuned!

N.B. The completed weather.sh script and the complete Conky configuration file are shown below.

Enjoy, Jim B.


weather.sh

#!/bin/sh

# weather.sh -- script to pull NWS weather data for a single location
#
# Can be used standalone or used with Conky configuration file as
# described in this techbits article.
#
# Code is in the public domain as of
# Wed Jul 24 14:32:54 EDT 2024
#
# Author Jim Brown, jpb@jimby.name
#

export LATVAR=35.9461
export LONVAR=-78.9703
export OUTDIR=.

echo 
echo "RESULTS: [${LATVAR}] [${LONVAR}] [${OUTDIR}]"

# Use the NWS /points API to  get the gridId, gridX, gridY values and save to temporary file points.json.
curl -s "https://api.weather.gov/points/${LATVAR}%2C${LONVAR}" > ${OUTDIR}/points.json

echo "Getting gridId, gridX, gridY, forecast, and radarStation"
# Get the gridId, gridX, gridY, and the forecast URL values from the properties list. Also pick up the radar station for later.
GRIDID=`cat ${OUTDIR}/points.json | jq -r '.properties.gridId'`
GRIDX=`cat ${OUTDIR}/points.json | jq -r '.properties.gridX'`
GRIDY=`cat ${OUTDIR}/points.json | jq -r '.properties.gridY'`
FORECASTURL=`cat ${OUTDIR}/points.json | jq -r '.properties.forecast'`
RADARSTATION=`cat ${OUTDIR}/points.json | jq -r '.properties.radarStation'`

echo "[$GRIDID] [$GRIDX] [$GRIDY] [$FORECASTURL] [$RADARSTATION]"

# Finally, get the forecast with the NWS gridpoints API.


echo "Getting forecast"
curl -s  "https://api.weather.gov/gridpoints/${GRIDID}/${GRIDX},${GRIDY}/forecast" -o ${OUTDIR}/forecast.json

echo "Getting the small NWS icon"
# Note: You must use the -r option on jq(1) or curl will fail when used this way.
export NWSICON=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].icon' | sed s/medium/small/`
curl -s  ${NWSICON} -o ${OUTDIR}/icon.png


# Get the loop map for later display.
echo "Getting loop map"
curl -s  "https://radar.weather.gov/ridge/standard/${RADARSTATION}_loop.gif" -o ${OUTDIR}/map_loop.gif

# OK, let's see the detailed forecast.

echo
echo "The forecast is:"

cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].name,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].detailedForecast,"\n"'

echo
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].name,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].detailedForecast,"\n"'

echo
echo

Conky Configuration File to Process and Display NWS Weather Data


-- Conky, a system monitor https://github.com/brndnmtthws/conky
--
-- This configuration file is Lua code. You can write code in here, and it will
-- execute when Conky loads. You can use it to generate your own advanced
-- configurations.
--
-- Try this (remove the `--`):
--
--   print("Loading Conky config")
--
-- For more on Lua, see:
-- https://www.lua.org/pil/contents.html
--
-- Conky Lua API: https://conky.cc/lua

-- Configuration settings: https://conky.cc/config_settings
conky.config = {
    alignment = 'top_left',
    background = false,
    border_width = 1,
    cpu_avg_samples = 2,
    default_color = 'white',
    default_outline_color = 'white',
    default_shade_color = 'white',
    double_buffer = true,
    draw_borders = false,
    draw_graph_borders = true,
    draw_outline = false,
    draw_shades = false,
    extra_newline = false,
    font = 'DejaVu Sans Mono:size=12',
    gap_x = 60,
    gap_y = 60,
    minimum_height = 5,
    minimum_width = 5,
    maximum_width = 600,
    net_avg_samples = 2,
    no_buffers = true,
    out_to_console = false,
    out_to_ncurses = false,
    out_to_stderr = false,
    out_to_wayland = false,
    out_to_x = true,
    own_window = true,
    own_window_class = 'Conky',
    own_window_type = 'normal',
    own_window_hints = 'undecorated,sticky,below,skip_taskbar,skip_pager',
    show_graph_range = false,
    show_graph_scale = false,
    stippled_borders = 0,
    update_interval = 1.0,
    uppercase = false,
    use_spacer = 'none',
    use_xft = true,
}

-- Variables: https://conky.cc/variables
conky.text = [[
${color grey}Uptime:$color $uptime
$hr
${execi 300 ~/src/conky_nws/weather.sh > /dev/null 2>&1}

Temp: ${execi 310 cat ~/src/conky_nws/.cache/forecast.json | jq -j  '.properties.periods[0].temperature,"°",.properties.periods[0].temperatureUnit'}

Short Forecast: ${execi 310 cat ~/src/conky_nws/.cache/forecast.json | jq -j  '.properties.periods[0].shortForecast'}

Prob of Precip: ${execi 310  cat ~/src/conky_nws/.cache/forecast.json | jq -j '.properties.periods[0].probabilityOfPrecipitation.value,"%"'}

Wind: ${execi 310 cat ~/src/conky_nws/.cache/forecast.json | jq -j '.properties.periods[0].windSpeed," from ",.properties.periods[0].windDirection'}

Detailed Forecast: ${execi 310 cat ~/src/conky_nws/.cache/forecast.json | jq -j  '.properties.periods[0].detailedForecast' | fmt -w 40}

${image ~/src/conky_nws/.cache/icon.png -p 150,40 -n}

]]