🌦️ [MOD RELEASE] Modernized weather.sh for Time-And-Weather / Skywarn Nodes

By Joe (KD2NFC)
Assisted by ChatGPT — with respect to the original work by KD5FMU and contributors.


:compass: Overview

If your AllStar or Skywarn node stopped announcing the weather, it’s because the original script depended on the AccuWeather RSS feed, which is now gone (HTTP 410 – page removed).

This update replaces that broken feed and brings the script into 2025 with two working weather providers:

You can choose either provider in your configuration file — no more editing the script to switch sources.


:puzzle_piece: What’s New

:white_check_mark: Fixed: Removed dead AccuWeather feed
:white_check_mark: Added: Open-Meteo API support (works worldwide, ZIP or ICAO)
:white_check_mark: Added: NOAA METAR API fallback (U.S. airports only, ICAO only)
:white_check_mark: Added: Day/night logic (no more “sunny” at midnight :crescent_moon:)
:white_check_mark: Configurable via weather.ini
:white_check_mark: Keeps compatibility with your existing Asterisk .gsm voice files


:gear: Installation

  1. Backup your old file:

    sudo cp /usr/local/sbin/weather.sh /usr/local/sbin/weather.sh.bak
    
    
  2. Drop in the new weather.sh
    (Download from the link or attachment provided with this post.)

    sudo cp weather.sh /usr/local/sbin/weather.sh
    sudo chmod +x /usr/local/sbin/weather.sh
    
    
  3. Update your configuration file:

    sudo nano /etc/asterisk/local/weather.ini
    
    

    Example:

    process_condition="YES"
    Temperature_mode="F"
    DEFAULT_PROVIDER="openmeteo"
    
    
    • process_condition: YES = include condition (e.g., “cloudy”), NO = just temperature

    • Temperature_mode: “F” or “C”

    • DEFAULT_PROVIDER: “openmeteo” or “metar”


:test_tube: Usage Examples

Command Description Works with
bash /usr/local/sbin/weather.sh KEWR v Fetch weather using METAR (ICAO airport code only) METAR
bash /usr/local/sbin/weather.sh 10314/EWR v Fetch weather using Open-Meteo (ZIP or city or ICAO) Open-Meteo
sudo perl /usr/local/sbin/saytime.pl KEWR 46596 Run your Time-and-Weather voice announcement Both
  • v = “view only” mode (prints results to screen without voice output)

:sun_behind_small_cloud: Output Example

METAR:

Running Joe KD2NFC version of weather.sh (provider: metar)
58°F, 14°C / cloudy

Open-Meteo:

Running Joe KD2NFC version of weather.sh (provider: openmeteo)
59°F, 15°C / clear


:speaker_high_volume: Condition Mapping

The script uses a simple set of condition words compatible with Asterisk sound files:

clear, sunny, cloudy, rain, snow, hail, fog, mist, thunderstorm

It will automatically locate .gsm files in:

/var/lib/asterisk/sounds/
/usr/local/share/asterisk/sounds/custom/


:wood: Optional Logging (for troubleshooting)

You can add this near the bottom of the script to keep a simple log:

echo "$(date '+%F %T') ${provider}: ${temp_f}°F / ${cond}" >> /var/log/weather.log


:warning: Notes

  • METAR requires a valid ICAO code (e.g., KEWR, KJFK, KLAX)
    → It won’t work with ZIP codes.

  • Open-Meteo accepts both ZIP codes and ICAO codes.

  • The script automatically falls back between providers if one fails.


:raising_hands: Credits

Do these instructions cover both ASL3 and HamVOIP? I have a weather program on my lone remaining HamVOIP node (of which I don’t even remember the name, but it absolutely has a /usr/local/sbin/weather.sh file). I hadn’t thought about it lately but it’s been silent for over a month, come to think of it…

I made this change for my ASL3. You may have the older version from HamVOIP but the files are the same and I didnt change the saytime.pl. Give it a try, back up your two files and try these. I noticed it wasn’t working for a few days and Accuweather’s RSS page was not found.

You can also start from scratch here, and then copy my files over the originals.

Everything I try to extract on my phone says corrupt file (the zip file, not the .pdf). I’ll go try on a proper computer next.

Your files here are corrupt, we can’t download them. Can you actually just post the contents of your files you changed so we can copy and paste and edit ours?

The pdf file needs to be renamed to .zip.

weather.sh

#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Time-And-Weather: weather.sh
#
# Originally developed by:  KD5FMU and contributors to the Time-And-Weather project
# Modified by:              Joe (KD2NFC)
# With assistance from:     ChatGPT (OpenAI)
# Date:                     2025-10-10
#
# Description:
#   Enhanced and modernized version of the original Time-And-Weather script.
#   - Providers: NOAA Aviation Weather METAR + Open-Meteo
#   - Night-aware condition mapping (no "sunny" at night)
#   - Compatible GSM word set; avoids duplicate playback
#
#   Config: /etc/asterisk/local/weather.ini
#       process_condition="YES"
#       Temperature_mode="F"            # "F" or "C"
#       DEFAULT_PROVIDER="metar"        # or "openmeteo"
#
#   Outputs:
#       /tmp/temperature
#       /tmp/condition.gsm
# ------------------------------------------------------------------------------

set -euo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"

# ---------- Config ----------
if [ -f /etc/asterisk/local/weather.ini ] ; then
  # shellcheck source=/dev/null
  . /etc/asterisk/local/weather.ini
else
  process_condition="YES"
  Temperature_mode="F"
  DEFAULT_PROVIDER="metar"
fi

case "${DEFAULT_PROVIDER:-}" in
  openmeteo|metar) : ;;
  *) DEFAULT_PROVIDER="metar" ;;
esac
provider="${DEFAULT_PROVIDER}"

echo "Running Joe KD2NFC version of weather.sh (provider: ${provider})"

destdir="/tmp"

# ---------- Helpers ----------
toupper(){ tr '[:lower:]' '[:upper:]'; }
k_prefix_if_needed(){
  local icao="$1"
  if [[ "$icao" =~ ^[A-Za-z]{3}$ ]]; then printf "K%s" "$(echo "$icao" | toupper)";
  else printf "%s" "$(echo "$icao" | toupper)"; fi
}
round(){ awk -v x="$1" 'BEGIN{printf "%.0f", x+0}'; }

# Map METAR codes → one GSM word we have (no "sunny" at night issue here by using "clear" for FEW)
metar_condition_word(){
  local m="$(printf '%s' "$1" | tr -s ' ')"
  [[ "$m" =~ (\+|-)?TS ]] && { echo "thunderstorm"; return; }
  [[ "$m" =~ FZRA|FZDZ|\+RA|-RA|RA ]] && { echo "rain"; return; }
  [[ "$m" =~ SN ]] && { echo "snow"; return; }
  [[ "$m" =~ PL ]] && { echo "hail"; return; }
  [[ "$m" =~ FG ]] && { echo "fog"; return; }
  [[ "$m" =~ BR|HZ|FU|DU|SA ]] && { echo "mist"; return; }
  [[ "$m" =~ OVC|BKN|SCT ]] && { echo "cloudy"; return; }
  [[ "$m" =~ FEW ]] && { echo "clear"; return; }     # CHANGED from "sunny" → "clear"
  [[ "$m" =~ (CLR|SKC) ]] && { echo "clear"; return; }
  echo "clear"
}

# Parse °F from METAR line
parse_metar_temp_f(){
  local m="$1" pair chunk sign num t_c_raw T s n1 n2 n3 tenths
  pair="$(printf '%s' "$m" | grep -oE ' [M]?[0-9]{2}/[M]?[0-9]{2} ' | head -n1 || true)"
  if [ -n "$pair" ]; then
    pair="$(printf '%s' "$pair" | tr -d ' ')"; chunk="${pair%%/*}"
    if [ "${chunk#M}" != "$chunk" ]; then sign='-'; num="${chunk#M}"; else sign=''; num="$chunk"; fi
    num="${num##0}"; [ -z "$num" ] && num=0; t_c_raw="${sign}${num}"
    awk -v c="$t_c_raw" 'BEGIN{printf "%.0f", (c*9/5)+32}'; return 0
  fi
  T="$(printf '%s' "$m" | grep -oE ' T[01][0-9]{7}\b' | head -n1 || true)"
  if [ -n "$T" ]; then
    T="${T# T}"; s="${T:0:1}"; n1="${T:1:1}"; n2="${T:2:1}"; n3="${T:3:1}"
    tenths="${n1}${n2}${n3}"
    if [ "$s" = "1" ]; then awk -v tc="-$tenths" 'BEGIN{printf "%.0f", ((tc/10)*9/5)+32}';
    else awk -v tc="$tenths"  'BEGIN{printf "%.0f", ((tc/10)*9/5)+32}'; fi
    return 0
  fi
  return 1
}

# Map Open-Meteo WMO weather_code → GSM word; honor is_day so 1–2 don't say "sunny" at night
openmeteo_condition_word(){
  local code="$1" is_day="${2:-1}"
  case "$code" in
    0) echo "clear" ;;
    1|2) [ "$is_day" = "1" ] && echo "sunny" || echo "clear" ;;  # CHANGED: night -> clear
    3) echo "cloudy" ;;
    45|48) echo "fog" ;;
    51|53|55|56|57) echo "rain" ;;
    61|63|65|66|67|80|81|82) echo "rain" ;;
    71|73|75|77|85|86) echo "snow" ;;
    95|96|99) echo "thunderstorm" ;;
    *) echo "clear" ;;
  esac
}

# Write condition.gsm from available word sound
write_condition_gsm(){
  local word="$(echo "$1" | awk '{print tolower($1)}')"
  local file1=""
  if [ -f "/var/lib/asterisk/sounds/${word}.gsm" ]; then
    file1="/var/lib/asterisk/sounds/${word}.gsm"
  else
    file1="$(locate "/${word}.gsm" 2>/dev/null | grep -v '/\.__MACOSX/' | head -n1 || true)"
    [ -z "$file1" ] && [ -f "/usr/local/share/asterisk/sounds/custom/${word}.gsm" ] && file1="/usr/local/share/asterisk/sounds/custom/${word}.gsm"
  fi
  if [ -n "$file1" ]; then
    cat "$file1" > "$destdir/condition.gsm"
  else
    rm -f "$destdir/condition.gsm"
  fi
}

# ---------- Args ----------
if [ -z "${1:-}" ]; then
  echo "Usage: weather.sh <ZIP/ICAO or ZIP/ICAO> [v]"
  exit 0
fi

arg="$1"
zip="${arg%%/*}"
icao="${arg#*/}"
[ "$icao" = "$arg" ] && icao="$arg"  # allow just ICAO/ZIP

# ---------- Provider: METAR ----------
fetch_metar(){
  local ICAO="$(k_prefix_if_needed "$icao")"
  local metar temp_f cond
  metar="$(curl --connect-timeout 15 -fsS "https://aviationweather.gov/api/data/metar?ids=${ICAO}&format=raw&hours=0" | head -n1 || true)"
  if [ -z "$metar" ]; then
    metar="$(curl --connect-timeout 15 -fsS "https://forecast.weather.gov/data/METAR/${ICAO}.1.txt" | grep -E "^${ICAO}\b" | head -n1 || true)"
  fi
  [ -z "$metar" ] && return 1
  temp_f="$(parse_metar_temp_f "$metar" || true)"; [ -z "$temp_f" ] && return 1
  cond="$(metar_condition_word "$metar")"
  echo "$temp_f|$cond"
}

# ---------- Provider: Open-Meteo (now requests is_day) ----------
fetch_openmeteo(){
  local q="${zip}"
  if [[ "$icao" != "$zip" ]]; then q="$icao"; fi

  local geo lat lon
  geo="$(curl -fsS "https://geocoding-api.open-meteo.com/v1/search?name=$(printf '%s' "$q" | sed 's/ /%20/g')&count=1" || true)"
  lat="$(echo "$geo" | sed -n 's/.*"latitude":\s*\([-0-9.]\+\).*/\1/p' | head -n1)"
  lon="$(echo "$geo" | sed -n 's/.*"longitude":\s*\([-0-9.]\+\).*/\1/p' | head -n1)"
  [ -z "$lat" ] || [ -z "$lon" ] && return 1

  local data temp code isday
  data="$(curl -fsS "https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current=temperature_2m,weather_code,is_day&temperature_unit=fahrenheit&timezone=auto" || true)"
  temp="$(echo "$data" | sed -n 's/.*"temperature_2m":\s*\([-0-9.]\+\).*/\1/p' | head -n1)"
  code="$(echo "$data" | sed -n 's/.*"weather_code":\s*\([0-9]\+\).*/\1/p' | head -n1)"
  isday="$(echo "$data" | sed -n 's/.*"is_day":\s*\([01]\).*/\1/p' | head -n1)"
  [ -z "$temp" ] && return 1
  local tf="$(round "$temp")"
  [ -z "$isday" ] && isday="1"  # assume day if missing, but usually present
  local cond="$(openmeteo_condition_word "${code:-0}" "${isday}")"
  echo "$tf|$cond"
}

# ---------- Run ----------
result=""
if [ "$provider" = "openmeteo" ] ; then
  result="$(fetch_openmeteo || true)"
  [ -z "$result" ] && result="$(fetch_metar || true)"
else
  result="$(fetch_metar || true)"
  [ -z "$result" ] && result="$(fetch_openmeteo || true)"
fi

[ -z "$result" ] && { echo "No Report"; exit 1; }

temp_f="${result%%|*}"
cond="${result##*|}"
ctemp="$(awk -v f="$temp_f" 'BEGIN{printf "%.0f", (f-32)*5/9}')"

# ---------- Output ----------
if [ "${2:-}" = "v" ]; then
  echo -e "${temp_f}°F, ${ctemp}°C / ${cond}"
  exit 0
fi

rm -f "$destdir/temperature" "$destdir/condition.gsm"
if [ "${Temperature_mode:-F}" = "C" ]; then
  echo "$ctemp" > "$destdir/temperature"
else
  echo "$temp_f" > "$destdir/temperature"
fi

if [ "${process_condition:-YES}" = "YES" ]; then
  write_condition_gsm "$cond"
fi

exit 0

weather.ini

#  Uncomment the process_condition and Temperature_mode
#  and set accordingly.
# Set this to use current WX condition. If "NO" then just temperature.
# wunderground is always no condition
process_condition="YES"
# Set this to "C" or "F" depending on your location
Temperature_mode="F"
# Your API key from weather.com
api_Key=""

#DEFAULT_PROVIDER="metar" or "openmeteo"
DEFAULT_PROVIDER="metar"
# End of weather.ini file


1 Like

Thanks for posting this fix. I had to make a slight change to the weather.sh script. I had to comment out the line below because it was showing on the Supermon2 dashboard instead of the weather information. With this line included, it prints this text, then the weather info. Supermon2 dashboardd was only showing this line of text. By commenting it out in the script, it now works great and shows the weather information.

I commented out this line in the script.

echo "Running Joe KD2NFC version of weather.sh (provider: ${provider})"

Trying to install on ASL2. After making the download executable, then running sudo ./time_weather.sh, it begins to download necessary files, but fails. Here is what I see:

repeater@K2ETS:~$ sudo ./time_weather.sh 07069 27274
Installing required packages...
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package plocate
Failed to install packages. Ensure you have an active internet connection.
repeater@K2ETS:~$

There Is an active internet connection, since I am doing this from a remote terminal using Putty.

You all should stop running old outdated software. I rewrite saytime_weather for asl3 that uses the default sound files. You are adding cruft and a less then ideal audio format for nothing. I actually had to rewrite the script for Freddie as his work only supports the USA, I made sure it would work for everyone around the world even tho I don't use it. You can always find the updated saytime_weather on my GitHub page

Installed on ASL3. Seemed successful. When run, I get:

root@node42690:/etc/asterisk# perl /usr/local/sbin/saytime.pl 08879 42690
/usr/local/sbin/weather.sh: line 100: hvwx: command not found
No Report

That script doesn't take any arguments. Please reread the instruction on KD5FMU’s GitHub page for the install.

I’ve written a direct drop in for the old weather.sh file at GitHub - kb2ear/aslweather: Drop in script to replace broken weather.sh in ASL using NOAA’s API

Not a drop in as it only US based weather. You can continue to use the old hamvoip version that Freddy made avaliable which has world wide support or you can use the saytime_weather from

which is a full rewrite of both and has been updated long before the old hamvoip version of Freddy’s was fixed.

Saytime_weather works on ASL3. Does not install correctly on ASL2. Anthing like this for ASL2?

hvwx is a byte compiled (obsfucated) PHP script added to HamVoIP in late 2023. None of the new scripts posted here or in the Supermon forum use it as it is only called by the weather.sh script included in hamvoip-misc-scripts-0.3-60 dated Dec 2, 2023, one of the later overall HamVoIP updates.

Thanks for reworking the program. I made changes to get it working for myself and shared it with the group. I will check out your version. Seems like you made a lot of enhancements. Did you redo the audio files?

My version uses the ulaw files, it will install a small set of weather sound files but that is all. It properly announced “Partly Cloudy" instead of just cloudy.

1 Like

What error are you seeing? The most likely issue would be the sound file path but not sure. I can setup an asl3 node on a pi 2b tonight and check tho.

Nice work, Jory.

I just installed and ran it on my ASL3 node and it reported the time as “Two Zero Five PM”, which, while correct, is unusual sounding as most in the USA would say it as “Two Oh Five PM”. Some of the other scripts I’ve seen have taken steps to replace the “Zero” with “Oh”.

Second, your README.md file has not been updated to reflect that -l does take the IACO identifier in place of the postal code. Right away I could tell the difference as open-meteo is not accurate for this location.