Conky and the Weatherman, Part II
In the previous article on this topic, we introduced Conky, and explored how it can be used to display weather data from the US National Weather Service, automatically retrieving the data and formatting it for display with Conky. In this article, we want to expand on those capabilities and get Conky to show all the relevant data including alerts and those moving weather maps.
To start, let’s decide what we actually want to see on the display. In my case, I want to see:
- The current time
- The last time the NWS data was refreshed
- Data for temperature, wind speed and direction, chance of precipitation, and the Heat Index. The Heat Index is an important metric to be aware of, especially in the summertime
- The current forecast for “now”, and the significant periods coming up (generally the next six hours. We’re calling these “dailies” in this article.
- Hourly data for the next 12 hours or so. We’re calling these “hourlies”.
- Alerts or alert updates. The NWS will send out an alert when major weather events are expected in the local area. We want to be able to read an alert or update with all the gory details.
- The weather map loop file for the locality we are interested in.
We will also have to decide how we regularly get this data from the NWS. In the earlier article, we had Conky run a script to do it using the execi command. While this can work in a simple case, it doesn’t work for more complex cases because the execution of the script is synchronous - there is no asynchronous execi (or exec) command in Conky. If the command is hung, so is Conky, so this is not the preferred approach.
Instead, we will use cron(8) to run the script using a simple entry in our local crontab(5) file. Here is the crontab entry for the script we are using:
## get the weather every 10 minutes on the 10 minute mark
0,10,20,30,40,50 * * * * $SHELL /home/jpb/conky_nws/weather+hourly.sh > /home/jpb/.cache/nws/_weather.log 2>&1
A couple of notes are in order. First, as you can see, we run the script every ten minutes on the 10 minute mark. There is a reason for this timing that we discuss below.
Second, the output - both stdout and stderr - are captured into the file _weather.log in the ~/.cache/nws directory. This directory is where all data is saved from all API calls in the script.
Finally, note that we have named the file weather+hourly.sh to distinguish it from the script in the earlier article. The reason is that we will get forecast data for the daily entries (“dailies”) and also for the hourly (“hourlies”) entries.
The remainder of this article will focus on the script - how we get the data - and then focus on the Conky configuration - how we use this data with Conky.
Understanding the Layout
Figure 1 shows the final layout that satisfies most of the requirements above. Data about alerts and updates is shown in Figure 2.
Here are notes on Figure 1:
- Shows the current time and also the time the data script was last run either through cron(8) or manually. If you are wanting to run the script manually, make sure you won’t overwrite the cron(8) run.
- Shows that there are no active alerts or updates.
- Shows a collection of Conky execgauges (see the Conky documentation.) These gauges show, in order, the current temperature, wind speed and direction, the current Heat Index, and the probability of precipitation.
- Displays the NWS “Short Forecast” - a minimal text description of the current weather.
- Shows two “dailies” forecasts - the current one is on top, and the next is on the bottom. The labels are part of the NWS data. This works out to about the next 24 hours.
- Shows the “hourlies” forecast data for the next several hours, spaced 30 minutes apart.
- Displays the NWS weather graphic - a stylized interpretation of the current weather. As noted in the first article, considerable work went into how to show this interpretation.
- Displays the NWS radar map loop. This is actually a .GIF animation file that is updated every two minutes. The loop file shows the time of the first plot, and all successive plots. There are usually nine individual plots in the image, so the total time span for the loop is about 18 minutes. Since we download the data every 10 minutes, the loop always shows the current weather pattern.
Data Collection Script weather+hourly.sh
“[Q]Why use a Bourne shell script, when a Python/Java/Go/XYZ script would have been so much easier/cleaner/faster/hip/nifty?”
Answer - three reasons. First, the Bourne Shell is well tested and is available on any modern Unix system. There are no surprises and it is unlikely (though possible) that it will ever need to be updated - the shell doesn’t change all that much.
Second, any Unix hacker can understand it with minimal difficulty.
Third, it’s very portable as is.
Before we dive in, there are a couple of notes to remember.
-
Some of the data is used as environment variables, some is captured directly into files, and some are both.
-
The data collection and formatting are loosely coupled to the Conky configuration script shown further below. I will point out the essential couplings in the comments.
-
Every run, we get the data or an alert/update if any. There is a symbolic link further down in the script that determines whether the alert/update is shown or whether the data is shown. This link is used in the Conky configuration file but the link itself is set by this script.
-
The NWS data is sometimes broken - i.e. not available from the API. This is an internal temporary issue with NWS processing. We can sometimes work around it, but if not we display “null” or some other explanation. Usually, these outages are fixed promptly and there is nothing to worry about on our end.
-
The script uses the echo(1) command to display data or processing steps. These are all collected in the _weather.log file for each run. If you are having trouble, check this log for details.
There are some utilities that are used in the script and/or the Conky configuration file:
- cut(1) - used to format text strings.
- jq(1) - used for extracting data out of JSON files.
- fmt(1) - used to format the forecast text strings.
- fold(1) - used to format the forecast text strings, particularly alert/update text.
- mpv(1) - used to display moving weather maps (animated .GIF files)
Ok, here is the nitty-gritty of the final data collection script:
#!/bin/sh
#
# weather+hourly.sh -- Script to pull NWS weather data for a single location.
# This script is best run under cron(8) at about 10 minute intervals.
#
# The script can be manually run with one parameter, "DATA", to force the symbolic link
# set below to display data instead of alerts or updates. See the comments below.
#
# Data is loosely coupled with the Conky configuration file. See comments.
#
# Code is in the public domain as of
# Fri Aug 9 21:40:45 EDT 2024
#
# Author Jim Brown, jpb@jimby.name
#
##### Preparations
#
# Output just the date and time on the first line - it's in the Conky config for "Last Run"
# at the top of the Conky display. The format specification follows the Conky output.
# Always use the '-j' option so you don't accidently set the date on your system.
date -j "+%Y-%m-%d %H:%M:%S"
# Home geocodes. Change these for your location. See the first article for guidance.
export LATVAR=35.9461
export LONVAR=-78.9703
# Set up the output directory. All data will be stored in this directory.
# Create the directory before running this script.
export OUTDIR=~/.cache/nws
# When we have an alert in progress, we sometimes want to see the data, not the alert.
# If the paramter "DATA" is requested, print the data, not the alert. See below.
if [ $# = 1 ]
then
export PARAM=$1
echo "Paramater [${PARAM}] requested"
fi
# Confirm our current info before we get started.
echo
echo "Location: [${LATVAR}] [${LONVAR}]; Outdir: [${OUTDIR}]; Parameter [${PARAM}]"
# We only want 12 hourlies, which are actually half-hour data segments.
export HOURLIES="1 2 3 4 5 6 7 8 9 10 11 12"
##### Initializations
#
# Get rid of previous files. Don't leave cruft laying around from a previous run.
#
echo "Cleaning output directory ${OUTDIR}"
rm ${OUTDIR}/*.png # The NWS image
# rm ${OUTDIR}/*.gif # Don't remove the map_loop.gif file - just overwrite it.
rm ${OUTDIR}/addr.json # The current address
rm ${OUTDIR}/points.json # The current geocoded NWS data
rm ${OUTDIR}/forecast.json # The current NWS forecast
rm ${OUTDIR}/forecast_hourly.json # The current NWS hourly forecasts - frequently broken
rm ${OUTDIR}/*.dat # Conky display data elements
rm ${OUTDIR}/*.txt # Conky display text - either DATA.txt or ALERT.txt
# Depending on the symbolic link _display.txt set below (at end)
# There is a difference between what is shown on the Conky display, and what is used in the data.
#
echo "Initializing all .view and .dat files" # Each hourly entry has it's own file.
for i in ${HOURLIES}
do
export idx=`expr ${i} - 1`
#echo "idx = $idx"
export tChunk=`expr ${i} \* 30` # compute minutes
export dTime=`date -j -v+${tChunk}M "+%R%p"` # display time
echo 0 > ${OUTDIR}/temp.${idx}.view # .view file is now zero
echo 0 > ${OUTDIR}/temp.${idx}.dat # .dat file is now zero
echo 0 > ${OUTDIR}/heaIdx.${idx}.view # .view file is now zero
echo 0 > ${OUTDIR}/heaIdx.${idx}.dat # .dat file is now zero
echo "0 mph" > ${OUTDIR}/wSpeed.${idx}.dat
echo "X" > ${OUTDIR}/wDir.${idx}.dat
echo 0 > ${OUTDIR}/relH.${idx}.dat
echo 0 > ${OUTDIR}/probP.${idx}.dat
echo 0 > ${OUTDIR}/shortF.${idx}.dat
# These are for debugging if needed.
# printf "%s: Temp:%d\F Wind:%s fr %s: Hum:%d%% HI:%.2f\F probP:%d%% %s\n" ${dTime} ${temp} ${wSpeed} ${wDir} ${relH} ${heaIdx} ${probP} "${shortF}" >> ${OUTDIR}/DATA.txt
# printf "%s: Temp:%d\F Wind:%s fr %s: Hum:%d%% HI:%.2f\F probP:%d%% %s\n" "00:00PM" "0" "0" "X" "0" "0.00" "0" "No Short" >> ${OUTDIR}/DATA.txt
done
##### Getting the NWS Data
# Now use the NWS /points API to get the gridId, gridX, gridY values and save to temporary file points.json.
echo curl --silent --show-error "https://api.weather.gov/points/${LATVAR}%2C${LONVAR}" \-\o ${OUTDIR}/points.json
curl --silent --show-error "https://api.weather.gov/points/${LATVAR}%2C${LONVAR}" -o ${OUTDIR}/points.json
echo "Getting gridId, gridX, gridY, forecast, and radarStation"
# Now 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"
echo curl --silent --show-error "https://api.weather.gov/gridpoints/${GRIDID}/${GRIDX},${GRIDY}/forecast" \-\o ${OUTDIR}/forecast.json
curl --silent --show-error "https://api.weather.gov/gridpoints/${GRIDID}/${GRIDX},${GRIDY}/forecast" -o ${OUTDIR}/forecast.json
echo "Getting hourly forecasts"
echo curl --silent --show-error "https://api.weather.gov/gridpoints/${GRIDID}/${GRIDX},${GRIDY}/forecast/hourly" \-\o ${OUTDIR}/forecast_hourly.json
curl --silent --show-error "https://api.weather.gov/gridpoints/${GRIDID}/${GRIDX},${GRIDY}/forecast/hourly" -o ${OUTDIR}/forecast_hourly.json
echo "Getting the large 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/large/`
echo curl --silent --show-error ${NWSICON} \-\o ${OUTDIR}/large_icon.png
curl --silent --show-error ${NWSICON} -o ${OUTDIR}/large_icon.png
# Code for the medium icon if you would rather use that.
#echo "Getting the medium NWS icon"
#export NWSICON=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].icon'`
#curl --silent --show-error ${NWSICON} -o ${OUTDIR}/icon_medium.png
# Get the loop map and map0 for later display.
echo "Getting loop map"
echo curl --silent --show-error "https://radar.weather.gov/ridge/standard/${RADARSTATION}_loop.gif" \-\o ${OUTDIR}/map_loop.gif
curl --silent --show-error "https://radar.weather.gov/ridge/standard/${RADARSTATION}_loop.gif" -o ${OUTDIR}/map_loop.gif
# OK, let's see the short forecast.
# The .name data element is the timeliness label "This afternoon", "Tonight", "Today", "Tomorrow" etc.
# The .name element is used in the Conky config.
echo
echo "The short forecast is:"
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].name,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].shortForecast,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].name,"\n"'
cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].shortForecast,"\n"'
# OK, let's see the detailed forecast.
echo
echo "The forecast is:"
export name0=`cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[0].name,":"'`
export detF0=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].detailedForecast'`
export shtF0=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[0].shortForecast'`
echo
export name1=`cat ${OUTDIR}/forecast.json | jq -rj '.properties.periods[1].name,":"'`
export detF1=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[1].detailedForecast'`
export shtF1=`cat ${OUTDIR}/forecast.json | jq -r '.properties.periods[1].shortForecast'`
# Save the forecasts in two special files .0 and .1 for the current period and the next period.
echo ${name0} > ${OUTDIR}/name.0.dat
echo ${detF0} > ${OUTDIR}/detF.0.dat
echo ${shtF0} > ${OUTDIR}/shtF.0.dat
echo ${name1} > ${OUTDIR}/name.1.dat
echo ${detF1} > ${OUTDIR}/detF.1.dat
echo ${shtF1} > ${OUTDIR}/shtF.1.dat
echo
echo "Get daily forecast first. NWS data indexes start at 1,"
echo "but we set idx to 0 for forecast.json, because jq(1) uses zero based numbering."
echo
# This date is just for display in _weather.log.
date -j
echo "Extracting hourly data if possible"
# now get the hourly data if possible. See the HOURLIES variable above. It starts with 1..
for i in ${HOURLIES}
do
export idx=`expr ${i} - 1` # Convert index to zero based numbering to cooperate with jq(1).
#echo "idx = $idx"
# Format the time. Compute time for 30 minute chunks in the future, with the '-v' flag. Very handy.
export tChunk=`expr ${i} \* 30`
export dTime=`date -j -v+${tChunk}M "+%R%p"`
# Checking the status is important. Read through this section carefully.
# Check status and take actions described below.
export returnStatus=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".status"`
# The JSON returnStatus variable will usually be "503" on a real error, but will not be the actual word "null".
# The actual word "null" for returnStatus indicates success and is handled in the "elif" clause below.
if [ "X${returnStatus}" != "Xnull" ]
then
echo "Error on hourly forecast : returnStatus = [${returnStatus}]"
echo `cat ${OUTDIR}/forecast_hourly.json | jq -r ".detail"`
echo "NOT OK"
#
# On the other hand, if the hourlies are broken, NWS uses the actual word "null" for that error condition.
# Values of "null" (the actual string with the word "null") will be found if the hourly forecasts are broken.
# If so, set them to "" which is what the shell considers null. We handle this case below in the printf(1) statement.
#
#
# If the hourlies fail, get the data for temp, wSpeed, wDir, and probP from the daily forecast.json .properties.period[0]
export temp=`cat ${OUTDIR}/forecast.json | jq -r ".properties.periods[0].temperature"`
export wSpeed=`cat ${OUTDIR}/forecast.json | jq -r ".properties.periods[0].windSpeed"`
export wDir=`cat ${OUTDIR}/forecast.json | jq -r ".properties.periods[0].windDirection"`
export relH="40" # There is no relativeHumidity in the daily forecast.json file, so just use 40. There is always some humidity.
export probP=`cat ${OUTDIR}/forecast.json | jq -r ".properties.periods[0].probabilityOfPrecipitation.value"`
export shortF="Broken Hourlies"
# Hourlies are broken, so there is no relative humidity value.
# If there is no relative humidity value, use 40 for the computation below.
# The Heat Index result should be close (almost identical) to the temperature for temperatures
# less than or equal to 80 degrees F.
# This Lua script computes the Heat Index. It must have exactly two parameters.
# I've placed the Lua script in the ~/.cache/nws directory, but it can be anywhere you want.
export heaIdx=`lua ${OUTDIR}/heat_index.lua ${temp:=0} ${relH:=40}`
# Print what we have from detailed forecast and broken hourlies. This output goes into DATA.txt
echo printf "%s: Temp:%d\F Wind:%s fr %s: Hum:%d%% HI:%.2f\F probP:%d%% %s\n" "${dTime}" "${temp:=0}" "${wSpeed:=0}" "${wDir:=X}" "${relH:=0}" "${heaIdx:=0.00}" "${probP:=0}" "${shortF:=No Short Forecast}" output to DATA.txt
printf "%s: Temp:%d\F Wind:%s fr %s: Hum:%d%% HI:%.2f\F probP:%d%% %s\n" "${dTime}" "${temp:=0}" "${wSpeed:=0}" "${wDir:=X}" "${relH:=0}" "${heaIdx:=0.00}" "${probP:=0}" "${shortF:='Broken hourlies'}" >> ${OUTDIR}/DATA.txt
elif [ "X${returnStatus}" = "Xnull" ] # The JSON .returnStatus for success will be the actual word "null".
then
echo "OK"
export temp=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".properties.periods[${idx}].temperature"`
export wSpeed=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".properties.periods[${idx}].windSpeed"`
export wDir=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".properties.periods[${idx}].windDirection"`
export relH=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".properties.periods[${idx}].relativeHumidity.value"`
export probP=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".properties.periods[${idx}].probabilityOfPrecipitation.value"`
export shortF=`cat ${OUTDIR}/forecast_hourly.json | jq -r ".properties.periods[${idx}].shortForecast"`
# If temp and relH parameter values are unset or null, substitute 0 (see man sh(1)).
# Neither should be zero at this point, unless something is really, really broken.
export heaIdx=`lua ${OUTDIR}/heat_index.lua ${temp:=0} ${relH:=0}`
# Same for these values. These go into DATA.txt
echo printf "%s: Temp:%d\F Wind:%s fr %s: Hum:%d%% HI:%.2f\F probP:%d%% %s\n" "${dTime}" "${temp:=0}" "${wSpeed:=0}" "${wDir:=X}" "${relH:=0}" "${heaIdx:=0.00}" "${probP:=0}" "${shortF:=No Short Forecast}" output to DATA.txt
printf "%s: Temp:%d\F Wind:%s fr %s: Hum:%d%% HI:%.2f\F probP:%d%% %s\n" "${dTime}" "${temp:=0}" "${wSpeed:=0}" "${wDir:=X}" "${relH:=0}" "${heaIdx:=0.00}" "${probP:=0}" "${shortF:='Broken hourlies'}" >> ${OUTDIR}/DATA.txt
fi
# Save values for use in Conky configuration in two ways.
# Values in .view files are actual values and are used in the Conky configuration to display actual values.
# Values in .dat files are capped in range 0 to 100 as all execgauge values must be between 0 and 100. See Conky documentation.
# Get rid of all decimal points and decimals for comparison.
# This code only applies to temperature and heat index.
# Temperature code first.
echo ${temp:=0} > ${OUTDIR}/temp.${idx}.view # Keep the real number in the .view file.
if [ `echo ${temp:=0} | cut -d '.' -f 1 - ` -ge 100 ]
then
export temp=100.00
echo ${temp:=0} > ${OUTDIR}/temp.${idx}.dat # Cap the number at 100 for the .dat file for the gauge to work
else
echo ${temp:=0} > ${OUTDIR}/temp.${idx}.dat
fi
# Heat Index code next.
echo ${heaIdx:=0} > ${OUTDIR}/heaIdx.${idx}.view # Real number in .view file.
if [ `echo ${heaIdx:=0} | cut -d '.' -f 1 - ` -ge 100 ]
then
export heaIdx=100.00
echo ${heaIdx:=0} > ${OUTDIR}/heaIdx.${idx}.dat # Cap the number at 100 for .dat file for the gauge.
else
echo ${heaIdx:=0} > ${OUTDIR}/heaIdx.${idx}.dat
fi
# These are from detailed forecast if hourlies are broken. See comments above.
echo ${wSpeed:=0 mph} > ${OUTDIR}/wSpeed.${idx}.dat
echo ${wDir:=0} > ${OUTDIR}/wDir.${idx}.dat
echo ${relH:=0} > ${OUTDIR}/relH.${idx}.dat
echo ${probP:=0} > ${OUTDIR}/probP.${idx}.dat
echo ${shortF:=No Short Forecast} > ${OUTDIR}/shortF.${idx}.dat
done
# Let's get a look at the data. You can see this output in _weather.log
cat ${OUTDIR}/DATA.txt
# add a blank line at the end of the data. Conky needs this extra line for sanity.
echo >> ${OUTDIR}/DATA.txt
##### ALERTS ######
#
# NWS sends out alerts at any time. Alerts are based on the WFO GridX,GridY. Process an alert if there is one.
# See material on https://docs.oasis-open.org/emergency/cap/v1.2/CAP-v1.2-os.html
# and https://www.weather.gov/documentation/services-web-api#/ (see the schmemas at the bottom of the page)
# for alert types, urgency, description, instructions, etc.
#
# The alert/update layout is handled in the Conky configuration file.
#
echo "curl --silent --show-error \"https://api.weather.gov/alerts/active?point=${LATVAR}%2C${LONVAR}&limit=500\" \-o ${OUTDIR}/alerts.json"
curl --silent --show-error "https://api.weather.gov/alerts/active?point=${LATVAR}%2C${LONVAR}&limit=500" -o ${OUTDIR}/alerts.json
# These are the fields we want to display for an alert. We need messageType as both a shell variable and data in the ALERT.txt file.
export MESSAGETYPE=`cat ${OUTDIR}/alerts.json | jq -r '.features[0].properties.messageType'` # The shell variable
# Note: we must fold(1) everything (actually just the headline and the description) to max width 75 characters to agree with Conky display.
cat ${OUTDIR}/alerts.json | jq -rj '.features[0].properties.messageType,"\n"' | tr "[:lower:]" "[:upper:]" > ${OUTDIR}/ALERT.txt # Force the messageType to upper case.
cat ${OUTDIR}/alerts.json | jq -rj '.features[0].properties.status,"\n"' >> ${OUTDIR}/ALERT.txt
cat ${OUTDIR}/alerts.json | jq -rj '.features[0].properties.event,"\n"' >> ${OUTDIR}/ALERT.txt
cat ${OUTDIR}/alerts.json | jq -rj '.features[0].properties.category,"\n"' >> ${OUTDIR}/ALERT.txt
cat ${OUTDIR}/alerts.json | jq -rj '.features[0].properties.headline,"\n"' | fold -s -w 75 >> ${OUTDIR}/ALERT.txt
export SEVERITY=`cat ${OUTDIR}/alerts.json | jq -r '.features[0].properties.severity'` # These four data elements are usually just one word.
export URGENCY=`cat ${OUTDIR}/alerts.json | jq -r '.features[0].properties.urgency'`
export CERTAINTY=`cat ${OUTDIR}/alerts.json | jq -r '.features[0].properties.certainty'`
export RESPONSE=`cat ${OUTDIR}/alerts.json | jq -r '.features[0].properties.response'`
echo "${SEVERITY}/${URGENCY}/${CERTAINTY}/${RESPONSE}" | tr "[:lower:]" "[:upper:]" >> ${OUTDIR}/ALERT.txt # So we put them all on one line for Conky display.
echo "" >> ${OUTDIR}/ALERT.txt
cat ${OUTDIR}/alerts.json | jq -rj '.features[0].properties.description,"\n"' | fold -s -w 75 >> ${OUTDIR}/ALERT.txt
##### Setting the symbolic link.
#
# Now figure out which file to display - ALERT.txt or DATA.txt. Both should fit in 79 characters.
# If the parameter "DATA" was supplied to the script, print the DATA even if there is an alert.
# This is mostly for use in a manual run of this script.
#
# Remove the old link.
rm ${OUTDIR}/_display.txt
if [ "X${MESSAGETYPE}" = "Xnull" ] # Compare to the actual string "null". See comments above.
then # There is no alert. Display the DATA.txt file.
ln -s ${OUTDIR}/DATA.txt ${OUTDIR}/_display.txt
else # Else, display NWS alert/update messages, unless parameter "DATA" was supplied in a manual run
if [ "X${PARAM}" = "XDATA" ]
then
ln -s ${OUTDIR}/DATA.txt ${OUTDIR}/_display.txt
else
ln -s ${OUTDIR}/ALERT.txt ${OUTDIR}/_display.txt
fi
fi
##### All done!
# For the map loop, here is the way to display it overlaid on the Conky display. We use mpv(1) to display the .GIF animation file.
#
# Note: "--loop-playlist" reopens the file every time. This is what we want. The next download will replace the current map
# and mpv will be none the wiser.
#
echo "Done. Run mpv --screen=0 --geometry=300x275+1290+30 --really-quiet --loop-playlist --no-border ${OUTDIR}/map_loop.gif & "
echo " and adjust geometry if needed"
#
Configuring Conky
Setting up the Conky configration file for displaying weather data is tricky.
Conky places images, text, and graphics on the drawing canvas with a virtual “drawing point”. The drawing point starts at the top left of the canvas, and is influenced by text length, font type, font size, image size and placement, and graph or gauge shape and size. The drawing point itself is not visible, but it can be understood by the effects of various commands. To achieve precise positioning it is very helpful to have an on-screen pixel ruler such as flruler (available as a package on FreeBSD and most linux distributions) and a screen magnifier. I used qt5-pixeltool from the KDE project (also available as a FreeBSD package) to perform the screen magnifications I needed.
If you just want to run the Conky configuration, copy the configuration file at the end of this article into a text file (myweather.conf) and run:
$ conky -c myweather.conf
To understand the configuration, read on in the next section titled “Building the Conky Configuration”.
Building the Conky configuration.
Figures 3 and 4 show the Conky configuration with line numbers.
and
The above configuration should display on the upper right of the root window, and will look something like Figure 5.
The large, unused space in the upper right is for the map loop image. You can display the map loop image with the following commands. Obviously, you will have to have run the data collection script at least one time successfully.
$ export OUTDIR=~/.cache/nws
$ mpv --screen=0 --geometry=300x275+1290+30 --really-quiet --loop-playlist --no-border ${OUTDIR}/map_loop.gif &
You may need to set the geometry parameter for your system. The image should be scaled down to 300x275 pixels. On my display, it fits nicely on top of the Conky window at “+1290+30”. Adjust as needed.
mpv(1) is capable of showing the animated .GIF file and with the –loop-playlist option it will reopen the file every time. This is convenient since we replace the loop file with every run of the data collection script.
Developing the Layout
The Conky configuration starts at line 5 in Figure 3. Of note first are the variables ‘minimum_width’, ‘maximum_width’, and ‘minimum_height’. These define a maximum Conky virtual canvas of 600x600 pixels. Conky doesn’t always use the entire width, unless the configuration commands require it.
Fonts 1, 2, and 3 are defined on lines 33-35. In this configuration only the default font (named “font”) is used.
Colors are defined on lines 37-46. Not all these colors are used. Both colors and fonts are easily modified. However, keep in mind that a mono-spaced font like “DejaVu Sans Mono” will be needed to line up data in column formats.
Most of the work in setting up a Conky configuration is identifying each type of object Conky can draw and determining it’s true layout.
The coordinate system used in the Conky canvas is based on standard graphical coordinates and these coordinates are zero based similar to graphics modes on many common systems. The top left pixel is at coordinates (0,0) and there is a horizontal line of pixels in the X direction at pixel (n,0) and in the Y direction at (0,n). Keeping the zero-based numbering in mind is essential.
Conky positions text with a default buffer of 5 pixels from the left and top (0,0), so the very first pixel of a text character is at (5,5). Positioning of lines of text and other objects can be changed with the ${offset n} and ${voffset n} where ’n’ may be positive or negative.
For this configuration using Deja Vue Sans Mono 10 point font, each character has some pixel margin above the first dot of the visible upper case character and some pixel margin below the baseline of the character where the descenders that exist on lower case ‘j’, ‘g’, and ‘p’, etc. live. See Figure 6 for how characters are aligned inside this space.
The default execgauge (and execigauge) object is actually 26 pixels high and 41 pixels wide. The arc of the gauge is 25 pixels high, but the needle on the gauge descends one pixel below the baseline of the arc. The gauge has a top margin of 6 pixels and a bottom margin of 10 pixels below the needle.
Images are drawn from the top left of the image and are capped to any width and height restrictions in the configuration if they exist. If they don’t exist, the image is drawn with its native dimensions which may extend beyond the right side and bottom of the Conky canvas.
With these details, we can now describe the positioning of each item - text, gauge, or image.
Line 50 shows the text “Current Time:” which starts at the upper left corner of the Conky canvas. The character actually starts on pixel coordinates (5,5). When drawing text, Conky starts drawing at the upper left point of the character as shown in Figure 6. Depending on the font size and font type, the subsequent characters get drawn accordingly. For a mono-spaced 10-point font, the character is 8 pixels wide and 16 pixels deep. A one pixel vertical spacer is added between lines.
Line 53 shows a vertical offet command, ${voffset 50}, in order to draw the first gauge. This command is on a new line after the word “No”, on the third line of text, so the horizontal coordinate goes back to the left margin (5 pixels from the left) and the drawing point coordinate starts on the next line after the one pixel spacer after the character “N” - which is at (5,56).
Therefore, the ${voffset 50} command starts counting at coordinates (5,56) and ends at coordinate (5,106) and draws the first execigauge (temperature). The drawing starts at the upper left corner of the gauge which has a 6 pixel margin at the top. The high point of the gauge is therefore on Y coordinate 112.
Line 53 also shows two execgauges side by side.
As noted above, the execgauges are 26 pixels from top of arc to bottom of needle, and have a top margin of 6 pixels above the arc and 10 pixels below the bottom of the needle.
Line 54 specifies to draw the word “Temp” which would normally be drawn at (5,148), but there is a ${voffset -10} which would then start the character at (5, 138), but there is an additional ${offset 5}, so the actual drawing point for the word “Temp” starts at (10, 138)
In a similar way, all the other configuration items can be precisely defined for each type of object.
As a guide, Figure 6 shows how the Conky “drawing point” is manipulated for the entire configuration. (The numbers are the drawing operations order, not configuration line numbers.)
Note - the drawing icons are approximate, but the math in the configuration file lines is correct.
Here is the final Conky configuration:
-- Conky configuration for NWS weather data.
-- URL: https://www.jimby.name/
-- Author: Jim Brown, jpb@jimby.name
-- Placed in the public domain Sat Aug 10 12:47:09 EDT 2024
--
conky.config = {
use_xft = true,
font = 'DejaVu Sans Mono:size=10',
update_interval = 1,
total_run_times = 0,
own_window = true,
own_window_type = 'normal',
own_window_transparent = true,
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
double_buffer = true,
draw_shades = false,
draw_outline = false,
draw_borders = false,
draw_graph_borders = true,
default_color = 'white',
alignment = 'top_right',
minimum_width = 600,
maximum_width = 600,
minimum_height = 600,
gap_x = 10,
gap_y = 10,
no_buffers = true,
uppercase = false,
cpu_avg_samples = 2,
net_avg_samples = 2,
override_utf8_locale = true,
-- Fonts: BebasNeue-Regular and weathericons-regular-webfont
font = 'DejaVu Sans Mono:size=10',
font1 = 'Iosevka Medium:size=9',
font2 = 'Iosevka Medium:size=9',
font3 = 'Iosevka Medium:bold:size=9',
font4 = 'Iosevka Medium:size=8',
font5 = 'Union City Blue:bold:size=18',
font6 = 'bebas neue:size=16',
font7 = 'crackdown r2 brk:bold:size=18',
-- Colors
color0 = '#FFFFFF', -- white
color1 = '#FF0000', -- red
color2 = '#008000', -- green
color3 = '#FFFF00', -- yellow
color4 = '#0000FF', -- blue
color5 = '#FF00FF', -- magenta
color6 = '#00FFFF', -- cyan
color7 = '#00FF00', -- lime
color8 = '#FFD700', -- gold
color9 = '#008080', -- teal
};
conky.text = [[
${color8}Current Time: ${time}${color}
${color8}Last Run: ${color8}${execi 10 head -1 ~/.cache/nws/_weather.log}${color}
${goto 5}${if_existing /home/jpb/.cache/nws/ALERT.txt ALERT}${color1}ALERT${else}${if_existing /home/jpb/.cache/nws/ALERT.txt UPDATE}${color3}UPDATE${else}${color7}No Alert or Update${endif}${endif}${color}
${voffset 50}${execigauge 30 cat ~/.cache/nws/temp.0.dat}${execigauge 30 cat ~/.cache/nws/wSpeed.0.dat}
${color}${voffset -10}${offset 5}Temp${offset 8}Wind
${color}${offset 15}°F${offset 20}${exec cat ~/.cache/nws/wDir.0.dat}
${color}${voffset -75}${offset 12}${exec cat ~/.cache/nws/temp.0.view}${offset 20}${exec cat ~/.cache/nws/wSpeed.0.dat}
${voffset 60}
${execigauge 30 cat ~/.cache/nws/heaIdx.0.dat}${execigauge 30 cat ~/.cache/nws/probP.0.dat}
${color}${voffset -10}${offset 5}Heat${offset 3}Precip
${color}${offset 8}Idx${offset 12}Prob%
${color}${voffset -75}${offset 0}${exec cat ~/.cache/nws/heaIdx.0.view}${offset 12}${exec cat ~/.cache/nws/probP.0.dat}
${voffset 80}${color6}Now: ${color}${exec cat ~/.cache/nws/shtF.0.dat | fold -s -w 32}${color}
${voffset 10}
${color6}${exec cat ~/.cache/nws/name.0.dat}${color} ${exec cat ~/.cache/nws/detF.0.dat | fold -s -w 60}
${color6}${exec cat ~/.cache/nws/name.1.dat}${color} ${exec cat ~/.cache/nws/detF.1.dat | fold -s -w 60}
${image ~/.cache/nws/large_icon.png -p 120,70 -n}
# _display.txt is a symbolic link pointing to ALERT.txt or DATA.txt. Set in weather+hourly.sh
${voffset 10}${font}${execi 5 cat ~/.cache/nws/_display.txt | cut -b 1-79}${font}${color}
#${voffset 10}${execi 5 cat ~/.cache/nws/_display.txt | fold -s -w 78}
#${voffset 10}${execi 5 cat ~/.cache/nws/ALERT.txt | cut -b 1-79}
#${voffset 10}${execi 5 cat ~/.cache/nws/ALERT.txt | fmt -w 79}
]];
Remember to start mpv(1) as noted at the end of the data collection script.
Conclusion
Your setup should now pull weather data from the US NWS based on your location every ten minutes and update the Conky display as configured.
Enjoy!