#!/bin/bash # Peri's sump pump daemon # For Beldandi # $Id: sumpd,v 1.18 2005/06/09 16:52:48 perette Exp $ METARSITE="weather.noaa.gov" METARDIR="data/observations/metar/stations" STATION="KROC" EMAIL="sumppump@perette.barella.org" BASE_RUNTIME=5 function get_metar_data { typeset place="/var/tmp/$arg0.$$.metar" ftp -n $METARSITE << EOF user anonymous $EMAIL cd $METARDIR get $STATION.TXT $place quit EOF if [ $? -ne 0 -o ! -s "$place" ] then rm -f "$place" return 1 fi cat "$place" rm "$place" return 0 } # The METAR parser is tuned to US metar reports. # Changes will be necessary for international use, but # it's probably a good starting point if needed. # The parser is probably also doomed if it gets a TAF report # instead of a METAR, which has a few different fields. function parse_metar_data { typeset file="$1" D="[0-9]" set $(egrep "^$STATION |^SPECI $STATION" "$file" | tail -1) # TAF - weather report; TAF AMD - amended weather # METAR - hourly report; SPECI - special; TESTM - test [ "$1" = "TESTM" ] && return 1 [ "$1" = "SPECI" -o "$1" = "METAR" -o "$1" = "TAF" ] && shift [ "$1" = "AMD" ] && shift expr "$2" : "$D$D$D$D$D${D}Z\$" >/dev/null || return 1 METAR_DAY=$(echo $2 | cut -c1-2) METAR_HOUR=$(echo $2 | cut -c3-4) METAR_MINUTE=$(echo $2 | cut -c5-6) let "METAR_SEC = ${METAR_MINUTE#0} * 60 + ${METAR_HOUR#0} * 3600 + ${METAR_DAY#0} * 86400" # Possible AUTOmated or CORrected observations [ "$3" = "AUTO" -o "$3" = "COR" ] && shift if expr "$3" : "$D$D$D$D${D}KT\$" >/dev/null || expr "$3" : "VRB$D${D}KT\$" >/dev/null then # 12035KT = 120 degrees @ 35 knotts METAR_GUST="" elif expr "$3" : "$D$D$D$D${D}G$D${D}KT\$" >/dev/null then # 12035G38KT = 120 degrees @ 35 knotts, 38 knott gusts METAR_GUST=$(echo $3 | cut -c7-8) else return 1 fi METAR_WINDDIR=$(echo $3 | cut -c1-3) METAR_WINDSPEED=$(echo $3 | cut -c4-5) [ "$METAR_WINDDIR" = "VRB" ] && METAR_WINDDIR="Variable" # If variable wind direction, then there might be a 150V210 # indicating variable winds between 150 and 210 degrees. expr "$4" : "$D$D${D}V$D$D$D" >/dev/null && shift # Visibility might be 1/4SM or 10SM or 2 1/2SM if expr "$4" : "$D/${D}SM\$" >/dev/null then METAR_VISIBILITY=0 elif expr "$4" : "$D$D*SM\$" >/dev/null then METAR_VISIBILITY=$(echo $4 | cut -dS -f1) elif expr "$4" : "$D$D*\$" >/dev/null && expr "$5" : "$D/${D}SM\$" >/dev/null then METAR_VISIBILITY="$4" shift 1 else return 1 fi shift 4 METAR_PRECIP_LEV="" METAR_PRECIP_INTENSITY="" METAR_PRECIP_DESCRIPTOR="" METAR_PRECIP_DESC="" METAR_PRECIP_TYPE="" METAR_PRECIP_TY="" METAR_PRECIP_OBSCURATION="" METAR_PRECIP_OBSC="" METAR_PRECIP_OTHER="" METAR_PRECIP_OTH="" # Precipitation status! # If there are multiple precipitation statuses reported, use # the first one which is the dominating one. if expr "$1" : '[+-]\{0,1\}\([A-Z][A-Z]\)\{1,5\}$' >/dev/null then typeset showers="$1" shift case "$showers" in -*) METAR_PRECIP_INTENSITY="Light" METAR_PRECIP_LEV="-" showers=$(echo "$showers" | cut -c2-) ;; +*) METAR_PRECIP_INTENSITY="Heavy" METAR_PRECIP_LEV="+" showers=$(echo "$showers" | cut -c2-) ;; esac while [ "$showers" != "" ] do typeset sc=$(echo "$showers" | cut -c1-2) case "$sc" in MI|PR|BC|DR|BL|SH|TS|FZ) METAR_PRECIP_DESC="$sc" ;; DZ|RA|SN|SG|IC|PL|GR|GS|UP) METAR_PRECIP_TY="$sc" ;; BR|FG|FU|VA|DU|SA|HZ|PY) METAR_PRESCIP_OBSC="$sc" ;; PO|SQ|FC|SS|DS) METAR_PRESCIP_OTH="$sc" ;; esac case "$sc" in VC) METAR_PRECIP_INTENSITY="In vicinity" ;; MI) METAR_PRECIP_DESCRIPTOR="Shallow" ;; PR) METAR_PRECIP_DESCRIPTOR="Partial" ;; BC) METAR_PRECIP_DESCRIPTOR="Broken" ;; DR) METAR_PRECIP_DESCRIPTOR="Low drifting" ;; BL) METAR_PRECIP_DESCRIPTOR="Blowing" ;; SH) METAR_PRECIP_DESCRIPTOR="Showers" ;; TS) METAR_PRECIP_DESCRIPTOR="Thunderstorm" ;; FZ) METAR_PRECIP_DESCRIPTOR="Freezing" ;; DZ) METAR_PRECIP_TYPE="Drizzle" ;; RA) METAR_PRECIP_TYPE="Rain" ;; SN) METAR_PRECIP_TYPE="Snow" ;; SG) METAR_PRECIP_TYPE="Snow grains" ;; IC) METAR_PRECIP_TYPE="Ice crystals" ;; PL) METAR_PRECIP_TYPE="Ice pellets" ;; GR) METAR_PRECIP_TYPE="Hail" ;; GS) METAR_PRECIP_TYPE="Small hail or snow pellets" ;; UP) METAR_PRECIP_TYPE="Unknown precipitation" ;; BR) METAR_PRECIP_OBSCURATION="Mist" ;; FG) METAR_PRECIP_OBSCURATION="Fog" ;; FU) METAR_PRECIP_OBSCURATION="Smoke" ;; VA) METAR_PRECIP_OBSCURATION="Volcanic ash" ;; DU) METAR_PRECIP_OBSCURATION="Widespread dust" ;; SA) METAR_PRECIP_OBSCURATION="Sand" ;; HZ) METAR_PRECIP_OBSCURATION="Haze" ;; PY) METAR_PRECIP_OBSCURATION="Spray" ;; PO) METAR_PRECIP_OTHER="Well-developed dust/sand whirls" ;; SQ) METAR_PRECIP_OTHER="Squalls" ;; FC) METAR_PRECIP_OTHER="Funnel cloud" ;; SS) METAR_PRECIP_OTHER="Sandstorm" ;; DS) METAR_PRECIP_OTHER="Duststorm" ;; esac showers=$(echo "$showers" | cut -c3-) done fi # Ignore any additional precipitation statuses. while expr "$1" : '[+-]\{0,1\}\([A-Z][A-Z]\)\{1,5\}$' >/dev/null do shift done # Cloud level entries: CLR or OVC020, BKN150, etc. while [ "$1" = "CLR" -o "$1" = "SKC" ] || expr "$1" : "[A-Z][A-Z][A-Z]$D$D$D[A-Z]*\$" >/dev/null || expr "$1" : "VV$D$D$D\$" >/dev/null do shift done expr "$1" : "M*$D$D*/M*$D$D*\$" >/dev/null || return 1 METAR_TEMPERATURE=$(echo $1 | cut -d/ -f1 | sed 's/M/-/') METAR_DEWPOINT=$(echo $1 | cut -d/ -f2 | sed 's/M/-/') expr "$2" : "A$D$D$D$D*\$" >/dev/null || return 1 METAR_ALTIMETER="$(echo $2 | cut -c2-3).$(echo $2 | cut -c4-5)" return 0 } function log { if [ "$log_file" != "" ] then echo "$(date '+%Y-%m-%d %H:%M:%S'):" $@ >> "$log_file" else echo "$(date '+%Y-%m-%d %H:%M:%S'):" $@ 1>&2 fi } function run_unit_test_now { typeset status=0 while read aline do echo "$aline" > "$temp" if ! parse_metar_data "$temp" then status=1 echo "Failed to parse:" echo "$aline" fi done << EOF KROC 121354Z 23009KT 10SM CLR 19/16 A3018 RMK AO2 SLP217 T01940161 KROC 131854Z 03010KT 10SM FEW250 21/15 A3026 RMK AO2 SLP244 T02060150 KROC 131754Z VRB06KT 10SM FEW030 SCT250 22/14 A3027 RMK AO2 SLP248 8/101 T02170144 10217 20167 58007 KROC 152318Z 04024G38KT 3SM RA FEW019 BKN030 OVC045 25/23 A2949 RMK AO2 PK WND 05041/2306 PRESFR P0004 TSNO SPECI KROC 152318Z AUTO 04024G38KT 3SM RA BR FEW019 BKN030 OVC045 25/23 A2949 RMK AO2 PK WND 05041/2306 PRESFR P0004 TSNO KROC 160153Z AUTO 01032G41KT 4SM HZ SCT024 BKN030 OVC037 26/23 A2948 RMK AO2 PK WND 02045/0131 RAB27E40 SLP980 P0002 T02560228 KROC 281154Z 00000KT 2 1/2SM BR BKN008 OVC065VV 15/14 A2993 RMK AO2 SLP136 8/6// T01500144 10167 20144 53003 KROC 311654Z COR 27017G24KT 10SM BKN039 OVC047TCU 13/07 A2973 RMK AO2 PK WND 27031/1624 SLP067 BINOVC SW AND DSNT N T01330067 KROC 061618Z 08012KT 1/2SM SN FZFG VV006 M03/M04 A3019 RMK AO2 P0000 KROC 120954Z 28011KT 8SM -SN BKN014 OVC018 M01/M01 A2960 RMK AO2 SLP031 P0000 T10061011 KROC 140505Z 34015G21KT 4SM -SN BR OVC016 M04/M06 A2991 RMK AO2 P0000 KROC 140554Z 34010KT 10SM -SN OVC019 M06/M09 A2994 RMK AO2 SLP146 P0002 60013 931013 4/001 8/7// T10611089 10000 21061 51042 EOF return $status } arg0=$(basename $0) temp="/var/tmp/$arg0.$$" config="$HOME/.sumpdrc" if [ "$1" = "RUN_UNIT_TEST_NOW" ] then run_unit_test_now && echo "$arg0: All tests passed." exit $? fi if [ -f ~/x10iod.ini ] then log_file=$(grep '^SumpLog=' ~/x10iod.ini | cut -d= -f2 | awk '{print $1}') fi # Snowpack is an approximation in millimeters if [ -s "$config" ] then snowpack=$(grep '^[ ]*SNOWPACK[ ]*=' "$config" | cut -d= -f2) expr "$snowpack" : '[0-9][0-9]*$' >/dev/null || snowpack=500 else snowpack=500 fi log 'sumpd $Revision: 1.18 $ started' metar_cached=0 need_cooling=false cooling_temp=0 exhaust=false while true do last_run_sump="${run_sump:-true}" last_snowpack="$snowpack" # Default actions to take: run_sump=true let "next_run = BASE_RUNTIME * 3" # See if it's time to get a new METAR, and try to get it. # BSD 'date' doesn't support +%1d to get single-digit values. eval "let metar_now=\"$(TZ="UTC+0" date '+ %d * 86400 + %H * 3600 + %M * 60 + %S' | sed 's/ 0/ /g')\"" if [ $metar_cached -gt $metar_now -o \ $((metar_cached + 3600)) -lt $metar_now ] then if get_metar_data > "$temp" then log "METAR: " "$(grep "^$STATION " "$temp")" if parse_metar_data "$temp" then log "Dated $METAR_DAY @ $METAR_HOUR:$METAR_MINUTE UTC. Temperature $METAR_TEMPERATURE dewpoint $METAR_DEWPOINT." log "Wind $METAR_WINDSPEED knots @ $METAR_WINDDIR, gusts ${METAR_GUST:-(none)}." log "Pressure $METAR_ALTIMETER, visibility $METAR_VISIBILITY miles." log "Precipitation report:" $METAR_PRECIP_INTENSITY $METAR_PRECIP_DESCRIPTOR $METAR_PRECIP_TYPE $METAR_PRECIP_OBSCURATION $METAR_PRECIP_OTHER metar_cached="$METAR_SEC" else log "Can not parse METAR data." metar_cached=0 fi fi rm "$temp" fi # If we have a valid METAR, react to it. if [ $metar_cached -gt 0 ] then case "$METAR_PRECIP_LEV${METAR_PRECIP_TY:-none}" in -DZ) let "next_run = BASE_RUNTIME * 6" ;; DZ) let "next_run = BASE_RUNTIME * 5" ;; +DZ) let "next_run = BASE_RUNTIME * 4" ;; -RA) let "next_run = BASE_RUNTIME * 3" ;; RA) let "next_run = BASE_RUNTIME * 2" ;; +RA) let "next_run = BASE_RUNTIME" ;; +*) run_sump=false next_run=60 let "snowpack = snowpack + 8" ;; -*) run_sump=false next_run=60 let "snowpack = snowpack + 2" ;; none) run_sump=false next_run=60 ;; *) run_sump=false next_run=60 let "snowpack = snowpack + 5" ;; esac if [ $METAR_TEMPERATURE -ge 0 -a $snowpack -gt 0 ] then # For every 2 degrees C, figure we'll melt # one mm of snow / 15 minutes. More if it's # raining. It's an approximation, though not # necessarily good one... But hey, it just # makes the sump pump go. let "melt = ($METAR_TEMPERATURE / 2)" let "snowpack = snowpack - $melt" [ $next_run -gt 15 -a $melt -gt 0 ] && next_run=15 [ $snowpack -lt 0 ] && snowpack=0 run_sump=true fi fi if [ $snowpack -ne $last_snowpack ] then log "Snowpack is now estimated at $snowpack mm." echo "SNOWPACK=$snowpack" > $config fi [ $next_run -gt 60 ] && next_run=60 hour=$(date '+%H') hour=${hour#0} # Cooling/exhaust fan control # Determine what temperature to provide cooling at. if [ $METAR_TEMPERATURE -ge 24 ] then need_cooling=true let cool_at=METAR_TEMPERATURE-4 [ $cool_at -lt 21 ] && cool_at=21 if [ $cool_at -gt $cooling_temp ] then cooling_temp=$cool_at log "Cooling temperature set to $cooling_temp" fi exhaust=true fi # If it's cool enough out, stop turning fans on. if [ $METAR_TEMPERATURE -le 21 ] then log "Cooling fans returned to user control." need_cooling=false cooling_temp=0 fi # If it's morning, or it's gotten cold, turn the fans off. if [ $hour -ge 4 -a $hour -le 6 ] then log "Shutting down any running fans before daytime." need_cooling=false action turn cooling_fan off cooling_temp=0 exhaust=false elif [ $METAR_TEMPERATURE -le 16 ] then log "Shutting down any running fans due to cool temperatures." need_cooling=false action turn cooling_fan off cooling_temp=0 exhaust=false fi if $need_cooling && [ $METAR_TEMPERATURE -le $cooling_temp ] then log "Enabling cooling fans." action turn cooling_fan on fi if $exhaust then log "Running exhaust fan." action turn exhaust_fan on (sleep 600; action turn exhaust_fan off) & fi if $run_sump || $last_run_sump then log "Running sump: run_sump=$run_sump, last_run_sump=$last_run_sump." action turn sump_pump on let "next_run = next_run - 1" sleep 60 fi # Adjust next time to a few minutes after the hour, when new METAR # should be up. updatetime=$(( 61 - $(date '+%M') )) [ $updatetime -gt 3 -a $updatetime -le $next_run -a $next_run -gt 15 ] && next_run=$updatetime # In case it accidentally started, always turn the sump off again. action turn sump_pump off sleep $((next_run * 60)) done exit 0