# HG changeset patch # User Daniel O'Connor # Date 1265685265 -37800 # Node ID 9dab44dcb331cb04aa133a30f97aa9f080bf8116 Initial commit of Greg's code from http://www.lemis.com/grog/tmp/wh1080.tar.gz diff -r 000000000000 -r 9dab44dcb331 LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ +This software is *not* licensed. If I haven't given you a copy, you +are not allowed to use it. If I have, I will have specified the +conditions. + +When it's finished, I'll think about the license. diff -r 000000000000 -r 9dab44dcb331 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,54 @@ +# $Id: Makefile,v 1.6 2009/12/20 00:40:24 grog Exp grog $ +# Makefile for wh-1080 +CC = cc +CFLAGS=-g -Wall -Wno-parentheses -I /usr/local/include +LOADLIBES=-lm -L /usr/local/lib -L /usr/local/lib/mysql -lusb -lmysqlclient + +HDRS= wh1080.h wh1080_dev.h +USRCS= util.c db.c +SRCS= wh1080.c ${USRCS} +DSRCS= local-compare.c ${USRCS} +RSRCS= report.c ${USRCS} +XSRCS= xreport.c ${USRCS} +YSRCS= yreport.c ${USRCS} +ALLSRC= ${SRCS} ${DSRCS} ${RSRCS} ${XSRCS} ${YSRCS} +UOBJS= ${USRCS:.c=.o} +OBJS= ${SRCS:.c=.o} +DOBJS= ${DSRCS:.c=.o} +ROBJS= ${RSRCS:.c=.o} +XOBJS= ${XSRCS:.c=.o} +YOBJS= ${YSRCS:.c=.o} +PROGS= wh1080 xreport yreport local-compare # report + +.c.s: + ${CC} $< -o $@ -S + +all: ${PROGS} + +wh1080: ${OBJS} ${HDRS} + ${CC} ${LOADLIBES} ${OBJS} -o $@ + +report: ${ROBJS} ${HDRS} + ${CC} ${LOADLIBES} ${ROBJS} -o $@ + +xreport: ${XOBJS} ${HDRS} + ${CC} ${LOADLIBES} ${XOBJS} -o $@ + +yreport: ${YOBJS} ${HDRS} + ${CC} ${LOADLIBES} ${YOBJS} -o $@ + +local-compare: ${DOBJS} ${HDRS} + ${CC} ${LOADLIBES} ${DOBJS} -o $@ + +# XXX get this right +DISTFILES = ${SRCS} ${HDRS} Makefile README LICENSE xreport.c yreport.c local-compare.c wundersend db plots web + +clean: + rm -f *~ *.o *.core wh1080 TAGS ${PROGS} + +dist: + echo DISTFILES ${DISTFILES} + tar czvf wh1080.tar.gz ${DISTFILES} + +.c.o: ${HDRS} + cc ${CFLAGS} -c -o $@ $< diff -r 000000000000 -r 9dab44dcb331 README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,132 @@ +$Id: README,v 1.4 2010/01/15 01:51:17 grog Exp grog $ + +This is an early snapshot of my WH-1080 code. Currently it works on +FreeBSD and NetBSD. I'm still actively developing it, and numerous +things will change. Note that many of the programs are buggy and may +crash, but all can be restarted without problem. + +At some time in the future, I'll create proper installation stuff for +this software. Currently I strongly recommend leaving it in the +structure it is. + +Greg Lehey, 20 December 2009 + +This hierarchy contains: + + Top level directory: + + - wh1080, a program that collects data from the station and + stores it in a MySQL database. This program frequently runs + into trouble with the USB connection, at least under FreeBSD. + When this happens, there appears to be no recovery except for + restarting the program, so that's what I do. I haven't been + able to get it to attach under Linux; freeing the kernel driver + (necessary under Linux) doesn't work. + + - report, which I think is obsolete, but which I haven't deleted + yet. + + - xreport, which takes the data from the database at regular + intervals and sends it to Wunderground + (http://www.wunderground.com/wundermap) + + - yreport, which creates a header file for the PHP page index.php + (see below). This currently frequently crashes, probably + because of inadequate error checking. + + - dereel-compare, which generates data for plotting comparative + temperatures between Dereel (where I live) and Ballarat (the + closest BoM station). + + - wundersend, a script to send data to Wunderground. Invoked by + xreport. + + I'm still working on code for a configuration file; in the + meantime, I enter the configuration manually in config.c. Most of + the current entries are still tailored to my specific environment, + and will definitely need change. + + Directory db + + - mkdb is SQL code to create the tables I'm using, currently + observations for local observations, remote_observations for + observations I pull in from Wunderground and the BoM, and + lastreport to keep track of what has been reported to remote + stations. + + - Script getremote-today, which pulls in observations fro + Wunderground and the BoM. It has a hard-coded list of station + IDs. + + - insertwunderground and insertBoM, which take the readings from + the script and insert them into the database. Currently very + kludgy. + + - tidyobservations, SQL code which fixes various breakage I've seen + in the incoming data. + + - Other stuff which I think is no longer needed. + + Directory plots + + - Script doplots, which creates various plots. Two optional + parameters, date and enddate. enddate is the first day not to be + considered in the plots (i.e. they go from 0:0 on date to 0:0 on + enddate). date defaults to today, and enddate defaults to date + + 1. Currently I'm still working on the multi-day plots, and + there's nothing to display them. + + - Various gnuplot scripts for the individual plots. Most won't need + changing, but clearly plot-Ballarat-Dereel.gnuplot is just an + example. Change the list at the top of gnuplots to get rid of it. + + Directory web + + - File index.php, which displays values for today and yesterday. It + doesn't access the database, so it can be put on remote web + servers. It accesses the file current.php (see config.c + config.php_header) and the plots created by doplot. + + - File db.php, effectively the same page as index.php, but it gets + its info directly from the database. It's also capable of + creating plots on demand. + + - File comparison.php, which displays comparative graphs of local + and remote stations, also generated by plots/doplots. + +Getting the pig to fly + + - Install MySQL, PHP and gnuplot. + - Fix config.c to match local conditions. + - Build the software. + - Create the web hierarchy. + - Create the database. By default the database is called 'weather'; + if you want something else, edit db/mkdb. Then: + + mysql < db/mkdb + + - Create the configuration. Edit db/mkconfig and do: + + mysql < db/mkconfig + + - Run: + + while :; do wh1080; done & + while :; do ./xreport -v -d ; done & + while :; do yreport; sleep 20; done & + +About the WH-108[01]: + + - I've seen from the comparative readings in my area + (http://www.lemis.com/grog/weather/comparison.php) that many + weather stations show very inaccurate barometric pressures. Mine + showed pressures that were uniformly about 14 hPa too low. I've + compensated for this by adding a correction factor in the config. + + - I still don't trust the rainfall readings. They are supposed to + be in units of 0.3 mm. I forget where this information comes + from, but it agrees with the information displayed on the unit. + But I also have a manual rain gauge, and it shows only about 70% + of the readings of the WH-1080. I'm still observing this, but it + looks like I'm due for another correction factor. + diff -r 000000000000 -r 9dab44dcb331 db.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,247 @@ +/* + * Report weather conditions to remote web sites. + * Database access and startup functions. + * + * Greg Lehey, 14 December 2009 + * + * $Id: db.c,v 1.5 2010/02/04 03:50:35 grog Exp $ + */ + +#include "wh1080.h" + +#define STAMPSIZE 32 /* size of time stamp to print out */ + +/* Command line options */ +int debug; /* -d */ +int update = 1; /* -n turns updating off */ +int recover = 0; /* -r: recover missed archive data (wh1080 only) */ +int verbose; /* -v */ +int once; /* -1 */ + +struct passwd *mypasswd; /* password information for default user */ + +/* MySQL stuff */ +MYSQL *mysql; +MYSQL_RES *mysql_result; +MYSQL_ROW mysql_row; +char mysql_querytext [1024]; /* build up queries here */ +struct config config; + +void db_login () +{ + if (! mysql_init (mysql)) + { + fprintf (stderr, "Can't initialize MySQL\n"); + exit (1); + } + mysql = mysql_init (mysql); + if (! mysql) + { + fprintf (stderr, "Can't initialize MySQL: insufficient memory\n"); + exit (1); + } + if (! mysql_real_connect (mysql, /* DB state */ + config.db_host, /* database host */ + config.db_user, + config.db_passwd, + config.db, + 0, + NULL, + 0 )) + { + fprintf (stderr, + "Can't connect to databaase: %s (%d)\n", + mysql_error (mysql), + mysql_errno (mysql) ); + exit (1); + } +} + +/* + * Perform a database query, return result to myresult. + */ +int domyquery (char *query, MYSQL_RES **myresult) +{ + if (mysql_query (mysql, query)) + { + fprintf (stderr, + "Can't query database: %s (%d)\n" + " query:%s\n", + mysql_error (mysql), + mysql_errno (mysql), + query ); + return 1; + } + *myresult = mysql_store_result (mysql); + return 0; +} + +/* + * Perform a database query, return result to implicit mysql_result. + */ +int doquery (char *query) +{ + return domyquery (query, &mysql_result); +} + +/* + * Make a copy of a string in malloced memory and return it. + */ +char *mycopy (char *string) +{ + char *result = malloc (strlen (string) + 1); + strcpy (result, string); + return result; +} + +/* + * Read in config from database. + * + * XXX We have a chicken and egg problem here: the config information includes + * the database name, but we need to know that before we start. For the time + * being we supply necessary information on the command line, with defaults. + * FIXME. + */ +int read_config (int argc, char *argv []) +{ + int col; + int arg; + + for (arg = 1; arg < argc; arg++) + { + if (! strcmp (argv [arg], "-d")) + debug = 1; + else if (! strcmp (argv [arg], "-n")) /* turn off DB updates */ + update = 0; + else if (! strcmp (argv [arg], "-v")) /* verbose */ + verbose = 1; + else if (! strcmp (argv [arg], "-1")) /* run once and exit */ + once = 1; + else + { + if (! config.station_id) + config.station_id = argv [arg]; + else if (! config.db_user) + config.db_user = argv [arg]; + else if (! config.db_passwd) + config.db_passwd = argv [arg]; + else if (! config.db_host) + config.db_host = argv [arg]; + else if (! config.db) + config.db = argv [arg]; + else + { + fprintf (stderr, "Too many arguments: %s\n", argv [arg]); + return -1; + } + } + } + + /* Set default values for config stuff above. */ + if (! config.station_id) + config.station_id = "mystation"; + if (! config.db_host) + config.db_host = "localhost"; + if (! config.db_user) + { + /* Find out user name and use it */ + mypasswd = getpwuid (getuid ()); + if (mypasswd) + config.db_user = mycopy (mypasswd->pw_name); + else + { + fprintf (stderr, "Can't get default user id\n"); + exit (1); + } + } + if (! config.db_passwd) + config.db_passwd = ""; + if (! config.db) + config.db = "weather"; + + if (! mysql_init (mysql)) + { + fprintf (stderr, "Can't initialize MySQL\n"); + exit (1); + } + mysql = mysql_init (mysql); + if (! mysql) + { + fprintf (stderr, "Can't initialize MySQL: insufficient memory\n"); + exit (1); + } + if (! mysql_real_connect (mysql, /* DB state */ + config.db_host, /* database host */ + config.db_user, + config.db_passwd, + config.db, + 0, + NULL, + 0 )) + { + fprintf (stderr, + "Can't connect to databaase: %s (%d)\n", + mysql_error (mysql), + mysql_errno (mysql) ); + exit (1); + } + + /* + * Read the rest of the config from specified database. + */ + + sprintf (mysql_querytext, + "SELECT version,\n" + " latitude,\n" + " longitude,\n" + " elevation,\n" + " pressure_error,\n" + " poll_interval,\n" + " address,\n" + " wunderground_station_id,\n" + " wunderground_passwd,\n" + " wunderground_report_interval,\n" + " weatheforyou_station_id,\n" + " weatheforyou_passwd,\n" + " weatherforyou_report_interval,\n" + " website_generation_interval,\n" + " db_table,\n" + " php_header,\n" + " comparefile\n" + "FROM config\n" + "WHERE station_id = \"%s\";\n", + config.station_id ); + if (doquery (mysql_querytext)) /* failure */ + exit (1); + + if (! (mysql_row = mysql_fetch_row (mysql_result))) /* got something */ + { + fprintf (stderr, "No configuration file found\n"); + exit (1); + } + + /* + * XXX here we should perform a version check. Do it when we introduce + * version 2. + */ + /* And this is filthy ugly. Fix it */ + col = 1; + config.latitude = atof (mysql_row [col++]); + config.longitude = atof (mysql_row [col++]); + config.elevation = atof (mysql_row [col++]); /* metres */ + config.pressure_error = atof (mysql_row [col++]); /* difference between actual and real pressure, hPa */ + config.poll_interval = atoi (mysql_row [col++]); /* time, in seconds, between reading the device */ + config.address = mycopy (mysql_row [col++]); /* Address to display on web pages */ + config.wunderground_station_id = mycopy (mysql_row [col++]); + config.wunderground_passwd = mycopy (mysql_row [col++]); + config.wunderground_report_interval = atoi (mysql_row [col++]); /* how many seconds between reports to Wunderground */ + config.weatheforyou_station_id = mycopy (mysql_row [col++]); + config.weatheforyou_passwd = mycopy (mysql_row [col++]); + config.weatherforyou_report_interval = atoi (mysql_row [col++]); /* how many seconds between reports to Weather for you */ + config.website_generation_interval = atoi (mysql_row [col++]); /* how many seconds between generating PHP header file */ + config.db_table = mycopy (mysql_row [col++]); /* table */ + config.php_header = mycopy (mysql_row [col++]); /* php header file with weather data */ + config.comparefile = mycopy (mysql_row [col++]); /* file to compare local weather stations */ + return 0; /* success */ +} + diff -r 000000000000 -r 9dab44dcb331 db/.gdbinit --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/.gdbinit Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,238 @@ +# set args < ~/household/weather/wunderground/2009-11-22 IVICI +set args < /home/grog/household/weather/BoM/94852-Ballarat/IDV60801.94852.axf +set complaints 1 +set print pretty +def xi +x/10i $eip +end +def xs +x/12x $esp +end +def xb +x/12x $ebp +end +def z +ni +x/1i $eip +end +def zs +si +x/1i $eip +end +def xp +echo \ \ \ \ \ \ esp:\ \ +output/x $esp +echo \ ( +output (((int)$ebp)-(int)$esp)/4-4 +echo \ words on stack)\n\ \ \ \ \ \ ebp:\ \ +output/x $ebp +echo \n\ \ \ \ \ \ eip:\ \ +x/1i $eip +echo Saved ebp:\ \ +output/x *(int*)$ebp +echo \ (maximum of\ \ +output ((*(int*)$ebp)-(int)$ebp)/4-4 +echo \ parameters possible)\nSaved eip:\ \ +x/1i *(int*)($ebp+4) +echo \nParm 1 at\ \ +output/x (int) ($ebp+8) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+8) +echo \nParm 2 at\ \ +output/x (int) ($ebp+12) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+12) +echo \nParm 3 at\ \ +output/x (int) ($ebp+16) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+16) +echo \nParm 4 at\ \ +output/x (int) ($ebp+20) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+20) +echo \n +end +def xx +echo \ \ \ \ \ \ ebp:\ \ +output/x vebp +echo Saved ebp:\ \ +output/x *(int*)vebp +echo \ (maximum of\ \ +output ((*(int*)vebp)-(int)vebp)/4-4 +echo \ parameters possible)\nSaved eip:\ \ +x/1i *(int*)(vebp+4) +echo \nParm 1 at\ \ +output/x (int) (vebp+8) +echo :\ \ \ \ \ +output (char*) *(int*)(vebp+8) +echo \nParm 2 at\ \ +output/x (int) (vebp+12) +echo :\ \ \ \ \ +output (char*) *(int*)(vebp+12) +echo \nParm 3 at\ \ +output/x (int) (vebp+16) +echo :\ \ \ \ \ +output (char*) *(int*)(vebp+16) +echo \nParm 4 at\ \ +output/x (int) (vebp+20) +echo :\ \ \ \ \ +output (char*) *(int*)(vebp+20) +echo \n +end +document xp +Show the register contents and the first four parameter +words of the current frame. +end +def xxp +echo \ \ \ \ \ \ esp:\ \ +output/x $esp +echo \n\ \ \ \ \ \ ebp:\ \ +output/x $ebp +echo \n\ \ \ \ \ \ eip:\ \ +x/1i $eip +echo Saved ebp:\ \ +output/x *(int*)$ebp +echo \ (maximum of\ \ +output ((*(int*)$ebp)-(int)$ebp)/4-4 +echo \ parameters possible)\nSaved eip:\ \ +x/1i *(int*)($ebp+4) +echo \nParm 1 at\ \ +output/x (int) ($ebp+8) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+8) +echo \nParm 2 at\ \ +output/x (int) ($ebp+12) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+12) +echo \nParm 3 at\ \ +output/x (int) ($ebp+16) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+16) +echo \nParm 4 at\ \ +output/x (int) ($ebp+20) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+20) +echo \nParm 5 at\ \ +output/x (int) ($ebp+24) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+24) +echo \nParm 6 at\ \ +output/x (int) ($ebp+28) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+28) +echo \nParm 7 at\ \ +output/x (int) ($ebp+32) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+32) +echo \nParm 8 at\ \ +output/x (int) ($ebp+36) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+36) +echo \nParm 9 at\ \ +output/x (int) ($ebp+40) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+40) +echo \nParm 10 at\ \ +output/x (int) ($ebp+44) +echo :\ \ \ \ \ +output (char*) *(int*)($ebp+44) +echo \n +end +document xxp +Show the register contents and the first ten parameter +words of the current frame. +end +def xp0 +x/12x *(int*)$esp +p *(int*)$esp +p (char*)*$esp +end +def xp1 +x/12x *(int*)($ebp+4) +p *(int*)($ebp+4) +p (char**)($ebp+4) +end +def xp2 +x/12x *(int*)($ebp+8) +p *(int*)($ebp+8) +p *(char**)($ebp+8) +end +def xp3 +x/12x *(int*)($ebp+12) +p *(int*)($ebp+12) +p (char**)($ebp+12) +end +def xp4 +x/12x *(int*)($ebp+16) +p *(int*)($ebp+16) +p (char**)($ebp+16) +end +document xp0 +Show the first parameter of current stack frame in various formats +end +document xp1 +Show the second parameter of current stack frame in various formats +end +document xp2 +Show the third parameter of current stack frame in various formats +end +document xp3 +Show the fourth parameter of current stack frame in various formats +end +document xp4 +Show the fifth parameter of current stack frame in various formats +end +def f0 +f 0 +xp +end +def f1 +f 1 +xp +end +def f2 +f 2 +xp +end +def f3 +f 3 +xp +end +def f4 +f 4 +xp +end +def f5 +f 5 +xp +end +document f0 +Select stack frame 0 and show assembler-level details +end +document f1 +Select stack frame 1 and show assembler-level details +end +document f2 +Select stack frame 2 and show assembler-level details +end +document f3 +Select stack frame 3 and show assembler-level details +end +document f4 +Select stack frame 4 and show assembler-level details +end +document f5 +Select stack frame 5 and show assembler-level details +end +document z +Single step 1 instruction (over calls) and show next instruction. +end +document xi +List the next 10 instructions from the current IP value +end +document xs +Show the last 12 words on stack in hex +end +document xb +Show 12 words starting at current BP value in hex +end diff -r 000000000000 -r 9dab44dcb331 db/RCS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/RCS Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +/home/Sysconfig/MasterRCS/home/grog/src/weather/WH-1080/db/RCS \ No newline at end of file diff -r 000000000000 -r 9dab44dcb331 db/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/README Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,15 @@ +Getting data from the web: + +Wunderground: + +fetch -o YYYY-M-D + + +Ballarat: +2009-12-7 http://www.wunderground.com/history/station/94852/2009/12/7/DailyHistory.html?req_city=NA&req_state=NA&req_statename=NA&MR=1&format=1 + +Sheoaks +2009-12-6 http://www.wunderground.com/history/station/94863/2009/12/6/DailyHistory.html?MR=1&format=1 + +Mt Helen: +2009-12-8 http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICMTHE3&day=8&year=2009&month=12&graphspan=day \ No newline at end of file diff -r 000000000000 -r 9dab44dcb331 db/convertme --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/convertme Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +insert into observations ( station_id, date, time, inside_humidity, inside_temp, inside_dewpoint, outside_humidity, outside_temp, outside_dewpoint, pressure_abs, pressure_msl, wind_speed, wind_gust, wind_direction_text, rain) select * from readings; \ No newline at end of file diff -r 000000000000 -r 9dab44dcb331 db/getremote-today --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/getremote-today Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,24 @@ +#!/bin/sh +# $Id: getremote-today,v 1.4 2010/01/02 03:01:02 grog Exp $ +# Get today's readings +# Wunderground +WEATHERDIR=/home/`whoami`/household/weather +STATIONS="IVICTORI86 IVICTORI94 IVICTORI102 IVICTORI115 IVICMTHE3 IVICREDA2" +PATH=$PATH:/home/grog/src/weather/WH-1080:/home/grog/src/weather/WH-1080/db:/usr/local/bin +DAY=`date +%d` +MONTH=`date +%m` +YEAR=`date +%Y` +for STATION in $STATIONS; do + DESTDIR=$WEATHERDIR/wunderground/$STATION + mkdir -p $DESTDIR + getwunderday $DAY $MONTH $YEAR $STATION $DESTDIR/$YEAR-$MONTH-$DAY +done +# BoM +# Ballarat airport +fetch -o - http://www.bom.gov.au/fwo/IDV60801/IDV60801.94852.axf | insertBoM | mysql weather +# Melbourne airport +fetch -o - http://www.bom.gov.au/fwo/IDV60801/IDV60801.94866.axf | insertBoM | mysql weather +# Sheoaks +fetch -o - http://www.bom.gov.au/fwo/IDV60901/IDV60901.94863.axf | insertBoM | mysql weather +mysql < /home/grog/src/weather/WH-1080/db/tidy-observations +/home/grog/src/weather/WH-1080/plots/doplots diff -r 000000000000 -r 9dab44dcb331 db/getwunderday --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/getwunderday Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,16 @@ +#!/bin/sh +# $Id: getwunderday,v 1.1 2009/12/08 23:32:32 grog Exp $ +# +# Get CSV data from Wunderground. +# +if [ $# -ne 5 ]; then + echo Usage: $0 day month year station dest + exit 1 +fi +DAY=$1 +MONTH=$2 +YEAR=$3 +STATION=$4 +DEST=$5 +fetch -o $DEST http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=$STATION\&month=$MONTH\&day=$DAY\&year=$YEAR\&format=1 +insertwunderground < $DEST $STATION | mysql weather diff -r 000000000000 -r 9dab44dcb331 db/insertBoM Binary file db/insertBoM has changed diff -r 000000000000 -r 9dab44dcb331 db/insertBoM.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/insertBoM.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,229 @@ +/* + * Read in Australian Bureau of Meteorology comma-delimited files and convert + * them into INSERT commands for MySQL. + * + * $Id: insertBoM.c,v 1.4 2010/01/03 05:05:28 grog Exp $ + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFSIZE 1024 +char buf [BUFSIZE]; +char sql_command [BUFSIZE]; + +/* We split up the input line into these fields, not all of which we use. */ +char *field [50]; /* pointers to beginnings of fields */ + +/* + * We alias these structs to the input field above. Unfortunately, the data + * returned from Sheoaks is different from Ballarat and Melbourne airports: + * Sheoaks doesn't report a QNH pressure. I should really parse the input files + * to check the fields, but it's pretty messy. + */ + +struct ballarat_row +{ + char *order; /* sort order (backwards!) */ + char *station_id; /* station ID, apparently always numeric */ + char *name; /* name of town, it would seem */ + char *history; /* this looks like another station ID */ + char *funnydate; /* date in useless form 0D/0H:0m[ap]m */ + char *date; /* date as YYYYMMDDHHmmSS */ + char *temp; /* temperature, °C */ + char *apparent_temp; /* apparent temperature */ + char *delta_t; /* wet bulb depression */ + char *dewpoint; /* dew point */ + char *wind_gust; /* wind gust speed */ + char *gust_knots; /* same again in knots */ + char *pressure_msl; /* pressure at mean sea level */ + char *pressure_qnh; /* QNH pressure, whatever that may be */ + char *rain; /* rain as text (can be "Tce") */ + char *humidity; /* relative humdity */ + char *wind_direction_text; /* text of direction */ + char *wind_speed; /* speed in km/h */ + char *wind_speed_knots; /* and in knots */ +} *ballarat_readings = (struct ballarat_row *) field; + +struct sheoaks_row +{ + char *order; /* sort order (backwards!) */ + char *station_id; /* station ID, apparently always numeric */ + char *name; /* name of town, it would seem */ + char *history; /* this looks like another station ID */ + char *funnydate; /* date in useless form 0D/0H:0m[ap]m */ + char *date; /* date as YYYYMMDDHHmmSS */ + char *temp; /* temperature, °C */ + char *apparent_temp; /* apparent temperature */ + char *delta_t; /* wet bulb depression */ + char *dewpoint; /* dew point */ + char *wind_gust; /* wind gust speed */ + char *gust_knots; /* same again in knots */ + char *pressure_msl; /* pressure at mean sea level */ + char *rain; /* rain as text (can be "Tce") */ + char *humidity; /* relative humdity */ + char *wind_direction_text; /* text of direction */ + char *wind_speed; /* speed in km/h */ + char *wind_speed_knots; /* and in knots */ +} *sheoaks_readings = (struct sheoaks_row *) field; + +void skipblanks (char **cp) +{ + while (**cp == ' ') + (*cp)++; +} + +/* + * Wind directions as text. BoM encloses them in "", so we do the same + */ +struct wind_directions +{ + char *text; /* name of direction */ + float direction; /* and the direction it represents */ +} wind_directions [] = { + {"\"N\"", 0}, + {"\"North\"", 0}, + {"\"NNE\"", 22.5}, + {"\"NE\"", 45}, + {"\"ENE\"", 67.5}, + {"\"E\"", 90}, + {"\"East\"", 90}, + {"\"ESE\"", 112.5}, + {"\"SE\"", 135}, + {"\"SSE\"", 157.5}, + {"\"S\"", 180}, + {"\"South\"", 180}, + {"\"SSW\"", 202.5}, + {"\"SW\"", 225}, + {"\"WSW\"", 247.5}, + {"\"W\"", 270}, + {"\"West\"", 270}, + {"\"WNW\"", 292.5}, + {"\"NW\"", 315}, + {"\"NNW\"", 337.5}}; + +int wind_direction_count = sizeof (wind_directions) / sizeof (struct wind_directions); +float previous_wind; +float wind_direction (char *text) +{ + int i; + if (! strcmp (text, "\"CALM\"")) + return previous_wind; + for (i = 0; i < wind_direction_count; i++) + if (! strcmp (wind_directions [i].text, text)) + return previous_wind = wind_directions [i].direction; + fprintf (stderr, "Unknown wind direction: %s\n", text); + return previous_wind; /* Sam Ting */ +} + +int main (int argc, char *argv []) +{ + char *cp; /* pointers in buffers */ + int fields; /* becomes number of fields in row */ + char date_text [11]; /* YYYY-MM-DD */ + char time_text [9]; /* HH-MM-SS */ + + if (argc > 1) + { + fprintf (stderr, "Usage: $0 < file\n"); + exit (1); + } + + while (1) + { + memset ((void *) buf, '\0', sizeof (buf)); /* don't trip over old stuff */ + if (! fgets (buf, BUFSIZE - 1, stdin)) + exit (0); /* empty file, just ignore */ + if (isdigit (buf [0])) /* valid row */ + { + fields = 0; + cp = buf; + while (1) + { + skipblanks (&cp); /* though there don't seem to be any */ + field [fields++] = cp; + while (*cp && (*cp != ',') && (*cp != '\n') && (*cp != ',')) + cp++; + if (! *cp) + break; + *cp++ = '\0'; /* delimit the string */ + } + /* + * buf now contains "fields" strings, and field (and thus "ballarat_row" + * and "sheoaks_row") contain pointers to them. + * + * Fix a few fields. + */ + strcpy (date_text, "YYYY-MM-DD"); + strcpy (time_text, "HH:MM:SS"); + /* The date info is surrounded with " */ + memcpy (date_text, &ballarat_readings->date [1], 4); /* YYYY */ + memcpy (&date_text [5], &ballarat_readings->date [5], 2); /* MM */ + memcpy (&date_text [8], &ballarat_readings->date [7], 2); /* MM */ + memcpy (time_text, &ballarat_readings->date [9], 2); /* HH */ + memcpy (&time_text [3], &ballarat_readings->date [11], 2); /* MM */ + memcpy (&time_text [6], &ballarat_readings->date [13], 2); /* MM */ + + if (strcmp (ballarat_readings->station_id, "94863")) /* Not Sheoaks */ + { + if (! strcmp (ballarat_readings->pressure_qnh, "-9999.0")) /* this seems to be a null value */ + ballarat_readings->pressure_qnh = "NULL"; + sprintf (sql_command, + "REPLACE INTO remote_observations (station_id, date, time, outside_temp, apparent_temp," + "delta_t, outside_dewpoint, wind_gust, pressure_msl, pressure_qnh, rain, outside_humidity, " + "wind_direction, wind_direction_text, wind_speed) " + "VALUES (\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", %s, %s, " + "%s, \"%s\", %4.1f, %s, \"%s\");", + ballarat_readings->station_id, + date_text, + time_text, + ballarat_readings->temp, + ballarat_readings->apparent_temp, + ballarat_readings->delta_t, + ballarat_readings->dewpoint, + ballarat_readings->wind_gust, + ballarat_readings->pressure_msl, + ballarat_readings->pressure_qnh, + ballarat_readings->rain, + ballarat_readings->humidity, + wind_direction (ballarat_readings->wind_direction_text), + ballarat_readings->wind_direction_text, + ballarat_readings->wind_speed ); + } + else /* currently only sheoaks */ + { + if (! strcmp (sheoaks_readings->pressure_msl, "-9999.0")) /* this seems to be a null value */ + sheoaks_readings->pressure_msl = NULL; + + sprintf (sql_command, + "REPLACE INTO remote_observations (station_id, date, time, outside_temp, apparent_temp," + "delta_t, outside_dewpoint, wind_gust, pressure_msl, rain, outside_humidity, " + "wind_direction, wind_direction_text, wind_speed) " + "VALUES (\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", %s, " + "%s, \"%s\", %4.1f, %s, \"%s\");", + sheoaks_readings->station_id, + date_text, + time_text, + sheoaks_readings->temp, + sheoaks_readings->apparent_temp, + sheoaks_readings->delta_t, + sheoaks_readings->dewpoint, + sheoaks_readings->wind_gust, + sheoaks_readings->pressure_msl, + sheoaks_readings->rain, + sheoaks_readings->humidity, + wind_direction (sheoaks_readings->wind_direction_text), + sheoaks_readings->wind_direction_text, + sheoaks_readings->wind_speed ); + } + puts (sql_command); + } + } + return 0; +} + diff -r 000000000000 -r 9dab44dcb331 db/insertwunderground Binary file db/insertwunderground has changed diff -r 000000000000 -r 9dab44dcb331 db/insertwunderground-0 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/insertwunderground-0 Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +grep ^200 $DATE | sed -f ~/src/weather/WH-1080/insertwunderground.sed diff -r 000000000000 -r 9dab44dcb331 db/insertwunderground.awk --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/insertwunderground.awk Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,6 @@ +/^20/ { + printf ("INSERT into wunderstuff (station_id, date, time, outside_temp, outside_dewpoint, outside_humidity, pressure_sea_level, visibility, wind_direction, wind_direction_text, wind_speed, wind_gust, rain, events, conditions) VALUES (\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\");\n", + STATION, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14 ); +} + + diff -r 000000000000 -r 9dab44dcb331 db/insertwunderground.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/insertwunderground.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,237 @@ +/* + * Read in Wunderground comma-delimited files and convert them into INSERT + * commands for MySQL. + * + * $Id: insertwunderground.c,v 1.5 2009/12/09 03:27:55 grog Exp $ + */ +#include +#include +#include +#include +#include +#include +#include +#include + + +#define BUFSIZE 1024 +char attribute_list [BUFSIZE]; +char buf [BUFSIZE]; +char sql_command [BUFSIZE]; +int attribute_count; +char *station; /* name of station */ +char *date; /* and date from second parameter, if present */ + +/* + * Convert the Wunderground field names to our table. This is confused by the + * fact that Wunderground can't make up its mind what to call the fields, so + * multiple names are possible, and they can have spaces in them. If we don't + * care about the field, we have a null translation. + * + * Some fields are problems: time is really two fields, date and time, and + * precipitation is sometimes in cm rather than mm. We special-case them. + */ +struct attributes +{ + char *wundername; /* name supplied in file */ + char *column_name; /* and the names we use */ +} attributes [] = { + {"Clouds", "clouds"}, + {"Conditions", "conditions"}, + {"Dew PointC", "outside_dewpoint"}, + {"DewpointC", "outside_dewpoint"}, + {"Events", "events"}, + {"Gust SpeedKm/h", "wind_gust"}, + {"HourlyPrecipMM", "rain"}, + {"Humidity", "outside_humidity"}, + {"PrecipitationCm", ""}, + {"PressurehPa", "pressure_msl"}, /* I assume this is correct */ + {"Sea Level PressurehPa", "pressure_msl"}, + {"SoftwareType", ""}, + {"TemperatureC", "outside_temp"}, + {"Time", "date"}, + {"TimeEST", "time"}, + {"VisibilityKm", "visibility"}, + {"Wind Direction", "wind_direction_text"}, + {"Wind SpeedKm/h", "wind_speed"}, + {"WindDirection", "wind_direction_text"}, + {"WindDirectionDegrees", "wind_direction"}, + {"WindSpeedGustKMH", "wind_gust"}, + {"WindSpeedKMH", "wind_speed"}, + {"dailyrainCM", "" }}; + +int columns = sizeof (attributes) / sizeof (struct attributes); + +int field [50]; /* build up a table of the fields here */ + +void skipblanks (char **cp) +{ + while (**cp == ' ') + (*cp)++; +} + +int main (int argc, char *argv []) +{ + char *cp; /* pointers in buffers */ + char *ep; /* pointers in buffers */ + char *fp; + char fieldname [64]; + int fields = 0; /* becomes number of fields in table */ + int myfield; + int i; + + if (argc < 2) + { + fprintf (stderr, "Usage: $0 < file STATION_ID\n"); + exit (1); + } + station = argv [1]; /* arg is station name */ + if (argc > 2) /* date specified too */ + date = argv [2]; + + /* + * The first useful line should be a list of field names. Read it in and + * decide what we want to use. + */ + do + { + if (! fgets (attribute_list, BUFSIZE - 1, stdin)) + exit (0); /* empty file, just ignore */ + } + while (strlen (attribute_list) < 4); /* I'm not sure how long the + * initial junk is, but this + * should cover it */ + /* + * Untidy copy of field names. + */ + cp = attribute_list; + while (1) + { + fp = fieldname; + skipblanks (&cp); + while ((*cp != ',') && (*cp !='0') && (*cp !='<')) + *fp++ = *cp++; /* copy the character */ + *fp = '\0'; /* and delimit it */ + for (i = 0; i < columns; i++) + if (! strcmp (fieldname, attributes [i].wundername)) /* found it */ + { + field [fields] = i; + break; + } + if (i == columns) /* not found */ + { + fprintf (stderr, "Warning: can't find field name %s\n", fieldname); + field [fields] = -1; /* don't use this field */ + } + if (strlen (attributes [i].column_name) == 0) /* no column */ + field [fields] = -1; /* */ + if ((*cp == '<') || (*cp == '\0')) /* end of useful info */ + break; + cp++; /* skip the delimiter */ + fields++; /* done another field */ + } + /* + * On exit from the loop, fields contains the number of fields, and field [] + * is a translation table. + */ + while (1) + { + if (! fgets (buf, BUFSIZE - 1, stdin)) + exit (0); /* empty file, just ignore */ + if (memcmp (buf, "20", 2) == 0) /* we have a line starting with at date */ + { + char *time; + + /* walk through the date field and convert it to two fields */ + sprintf (sql_command, "REPLACE INTO remote_observations (station_id, date, time"); + /* first get the names */ + for (myfield = 1; myfield < fields; myfield++) + { + if (field [myfield] >= 0) + sprintf (&sql_command [strlen (sql_command)], ", %s", attributes [field [myfield]].column_name); + } + sprintf (&sql_command [strlen (sql_command)], ") VALUES ("); + /* and now the values */ + cp = buf; + while (*cp != ' ') + cp++; + *cp++ = '\0'; /* split first field */ + skipblanks (&cp); /* this shouldn't be necessary */ + time = cp; /* second is time */ + while (*cp != ',') + cp++; /* delimit */ + *cp++ = '\0'; /* delimit second field */ + /* + * We now have buf pointing to the date, time pointing to the time, both + * \0 delimited, and cp pointing to the rest of the buffer. + */ + sprintf (&sql_command [strlen (sql_command)], + "\"%s\", \"%s\", \"%s\"", + station, + buf, + time ); + /* + * Do the rest + */ + for (myfield = 1; myfield < fields; myfield++) + { + skipblanks (&cp); + ep = cp; + while ((*ep != ',') && (*ep != '\n')) + ep++; + *ep++ = '\0'; /* make a string of it */ + if (field [myfield] >= 0) + sprintf (&sql_command [strlen (sql_command)], ", \"%s\"", cp); + cp = ep; /* point to next field */ + /* + * Strictly speaking this isn't necessary, since we have a counted + * number of fields, but I don't trust the data. + */ + if (*cp == '\0') + break; + } + sprintf (&sql_command [strlen (sql_command)], ");"); + puts (sql_command); + } + else if (isdigit (buf [0])) /* must be a time */ + { + sprintf (sql_command, "REPLACE INTO remote_observations (station_id, date"); + + + /* first get the names */ + for (myfield = 0; myfield < fields; myfield++) + { + if (field [myfield] >= 0) + sprintf (&sql_command [strlen (sql_command)], ", %s", attributes [field [myfield]].column_name); + } + sprintf (&sql_command [strlen (sql_command)], ") VALUES (\"%s\", \"%s\"", + station, + date ); + /* + * Do the rest + */ + cp = buf; + for (myfield = 0; myfield < fields; myfield++) + { + skipblanks (&cp); + ep = cp; + while ((*ep != ',') && (*ep != '\n')) + ep++; + *ep++ = '\0'; /* make a string of it */ + if (field [myfield] >= 0) + sprintf (&sql_command [strlen (sql_command)], ", \"%s\"", cp); + cp = ep; /* point to next field */ + /* + * Strictly speaking this isn't necessary, since we have a counted + * number of fields, but I don't trust the data. + */ + if (*cp == '\0') + break; + } + sprintf (&sql_command [strlen (sql_command)], ");"); + puts (sql_command); + } + } + return 0; +} + diff -r 000000000000 -r 9dab44dcb331 db/insertwunderground.sed --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/insertwunderground.sed Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +s:^\(2...-..-..\) :INSERT into wunderstuff (station_id, date, time, outside_temp, outside_dewpoint, outside_humidity, pressure_sea_level, visibility, wind_direction, wind_direction_text, wind_speed, wind_gust, rain, events, conditions) VALUES ('$STATION', '\1'): \ No newline at end of file diff -r 000000000000 -r 9dab44dcb331 db/insertwunderground.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/insertwunderground.sh Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +grep ^200 $DATE | sed 's: :,:' | awk -F , -v STATION=$STATION -f ~/src/weather/WH-1080/db/insertwunderground.awk diff -r 000000000000 -r 9dab44dcb331 db/junk/getremote --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/junk/getremote Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,8 @@ +#!/bin/sh +# $Id$ +# +# Get data from specified remote web sites. This needs to be +# customized. +# +# URLs to fetch +URLS=" diff -r 000000000000 -r 9dab44dcb331 db/junk/getwunder --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/junk/getwunder Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,8 @@ +#!/bin/sh +# $Id$ +# +# Get CSV data from Wunderground. +# +# Usage: $0 day month dest + +2009-12-8 http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICMTHE3&day=8&year=2009&month=12&graphspan=day diff -r 000000000000 -r 9dab44dcb331 db/mkconfig --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/mkconfig Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,93 @@ +/* + * Create MySQL config + * Edit to appropriate values before running. + * $Id: mkdb,v 1.3 2009/12/20 00:44:15 grog Exp grog $ + */ +insert into config +( version, /* format version number */ + latitude, /* In ° */ + longitude, + elevation, /* metres */ + pressure_error, /* difference between actual and real, hPa */ + poll_interval, + station_id, /* this is the one we use for the database */ + address, /* Address to display on web pages */ + wunderground_station_id, + wunderground_passwd, + wunderground_report_interval, /* how many seconds between reports to Wunderground */ + weatheforyou_station_id, + weatheforyou_passwd, + weatherforyou_report_interval, /* how many seconds between reports to Weather for you */ + website_generation_interval, /* how many seconds between generating PHP header file */ + db_host, /* database on this host */ + db_user, /* user ID on host */ + db_passwd, /* and password */ + db, /* database name */ + db_table, /* table */ + php_header, /* php header file with weather data */ + comparefile) /* file to compare local weather stations */ +values ( + /* version */ 1, /* don't change this */ + /* latitude */ -37.8185, + /* longitude */ 143.739473, + /* elevation */ 340, /* elevation of the station in metres */ + /* pressure_error */ -13.5, /* offset of pressure measurements, hPa */ + /* poll_interval */ 60, /* poll once a minute */ + /* station_id */ "MYID", + /* address */ "MYADDRESS", + /* wunderground_station_id */ "MYID", + /* wunderground_passwd */ "insecure", + /* wunderground_report_interval */ 300, /* how many seconds between reports to Wunderground */ + /* weatheforyou_station_id */ "", + /* weatheforyou_passwd */ "", + /* weatherforyou_report_interval */ 300, /* how many seconds between reports to Weather for you */ + /* website_generation_interval */ 60, /* how many seconds between generating PHP header file */ + /* db_host */ "dereel.lemis.com", + /* db_user */ "grog", + /* db_passwd */ "", + /* db */ "weather", + /* db_table */ "observations", + /* php_header */ "/home/grog/public_html/weather/current.php", + /* comparefile */ "/home/grog/public_html/weather/BoM-compare" +); + + +insert into config +( version, /* format version number */ + latitude, /* In ° */ + longitude, + elevation, /* metres */ + pressure_error, /* difference between actual and real, hPa */ + poll_interval, /* between reading the device */ + station_id, /* this is the one we use for the database */ + address, /* Address to display on web pages */ + wunderground_station_id, + wunderground_passwd, + wunderground_report_interval, /* how many seconds between reports to Wunderground */ + weatheforyou_station_id, + weatheforyou_passwd, + weatherforyou_report_interval, /* how many seconds between reports to Weather for you */ + website_generation_interval, /* how many seconds between generating PHP header file */ + db_table, /* table */ + php_header, /* php header file with weather data */ + comparefile) /* file to compare local weather stations */ +values ( + /* version */ 1, /* don't change this */ + /* latitude */ -37.8185, + /* longitude */ 143.739473, + /* elevation */ 340, /* elevation of the station in metres */ + /* pressure_error */ -13.5, /* offset of pressure measurements, hPa */ + /* poll_interval */ 60, /* poll once a minute */ + /* station_id */ "Dereel", + /* address */ "Dereel, Victoria, Australia", + /* wunderground_station_id */ "IVICTORI124", + /* wunderground_passwd */ "breached", + /* wunderground_report_interval */ 300, /* how many seconds between reports to Wunderground */ + /* weatheforyou_station_id */ "", + /* weatheforyou_passwd */ "", + /* weatherforyou_report_interval */ 300, /* how many seconds between reports to Weather for you */ + /* website_generation_interval */ 60, /* how many seconds between generating PHP header file */ + /* db_table */ "observations", + /* php_header */ "/home/grog/public_html/weather/current.php", + /* comparefile */ "/home/grog/public_html/weather/BoM-compare", +); diff -r 000000000000 -r 9dab44dcb331 db/mkdb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/mkdb Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,68 @@ +/* + * Create MySQL databases + * $Id: mkdb,v 1.3 2009/12/20 00:44:15 grog Exp grog $ + */ +create database if not exists weather; +use weather; +create table if not exists observations +(station_id char (16), + date date, + time time, + inside_humidity smallint null, /* humidity in percent */ + inside_temp float null, /* inside temperature */ + inside_dewpoint float null, /* inside dewpoint temperature */ + outside_temp float null, /* outside temperature */ + outside_dewpoint float null, /* outside dewpoint temperature */ + apparent_temp float null, /* apparent outside temperature */ + delta_t float null, /* delta to wet bulb temperature */ + outside_humidity smallint null, /* humidity in percent */ + pressure_abs float null, /* absolute pressure, hPa */ + pressure_msl float null, /* sea-level relative pressure, hPa */ + pressure_qnh float null, /* qnh pressure, hPa */ + visibility float null, + wind_direction float, /* wind direction, in 45° increments */ + wind_direction_text char (6) null, /* wind direction, text */ + wind_speed float null, /* wind speed in km/h */ + wind_gust float null, /* wind gust speed in km/h */ + rain float null, /* rainfall in mm */ + events text null, /* Not sure what this is */ + conditions text null, /* Not sure what this is */ + clouds text null, /* but what? */ + primary key (date, time, station_id)); +create table if not exists remote_observations like observations; + +/* Time of last report for this station to this remote site */ +create table if not exists lastreport +(station_id char (16), /* which station? */ + report_id char (16), /* and which remote site? */ + date timestamp, /* last report */ + primary key (station_id, report_id) +); + +/* Configuration */ +create table if not exists config +( version int, /* format version number */ + latitude float, /* In ° */ + longitude float, + elevation float, /* metres */ + pressure_error float, /* difference between actual and real pressure, hPa */ + poll_interval int, /* time, in seconds, between reading the device */ + station_id char (16), /* this is the one we use for the database */ + address text, /* Address to display on web pages */ + wunderground_station_id text, + wunderground_passwd text, + wunderground_report_interval int, /* how many seconds between reports to Wunderground */ + weatheforyou_station_id text, + weatheforyou_passwd text, + weatherforyou_report_interval int, /* how many seconds between reports to Weather for you */ + website_generation_interval int, /* how many seconds between generating PHP header file */ + db_host text, /* database on this host */ + db_user text, /* user ID on host */ + db_passwd text, /* and password */ + db text, /* database name */ + db_table text, /* table */ + sharefile text, /* shared segment file */ + php_header text, /* php header file with weather data */ + comparefile text, /* file to compare local weather stations */ + primary key (station_id) +); diff -r 000000000000 -r 9dab44dcb331 db/tidy-observations --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db/tidy-observations Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,19 @@ +/* + * $Id: tidy-observations,v 1.4 2010/01/03 03:03:17 grog Exp grog $ + * + * Tidy up messed-up values returned from various stations. + */ +USE weather +UPDATE remote_observations SET wind_gust = NULL WHERE wind_gust < 0; +UPDATE remote_observations SET rain = NULL WHERE rain < 0; +/* Never likely to go below this temperature here */ +UPDATE remote_observations SET outside_temp = NULL WHERE outside_temp < -10; +/* This one is 0° F, which must mean something special to Wunderground */ +UPDATE remote_observations SET outside_dewpoint = NULL WHERE outside_dewpoint = -17.8; +UPDATE remote_observations SET wind_gust = NULL WHERE wind_gust < 0; +UPDATE remote_observations SET rain = NULL WHERE rain < 0; +UPDATE remote_observations SET wind_speed = NULL WHERE wind_speed < 0; +UPDATE remote_observations SET outside_humidity = NULL WHERE outside_humidity < 0; +UPDATE remote_observations SET wind_direction = NULL WHERE wind_direction < 0; +UPDATE remote_observations SET pressure_msl = NULL WHERE pressure_msl < 900 OR pressure_msl > 1100; +UPDATE remote_observations SET outside_dewpoint = NULL WHERE outside_dewpoint < -20; diff -r 000000000000 -r 9dab44dcb331 local-compare.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/local-compare.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,390 @@ +/* + * Create text files with a table comparing local temperature and barometric + * pressurs to remote observations. + * + * Format: + * + * Date Difference # legible date + * + * Greg Lehey, 17 December 2009 + * + * FIXME. This needs rethinking. In particular, startup parameters are a mess. + * Read on, brave programmer. + * + * $Id: local-compare.c,v 1.5 2010/02/07 03:38:26 grog Exp $ + */ + +#include "wh1080.h" +#include + +#define STAMPSIZE 32 /* size of time stamp to print out */ + +char *stations [] = {"94852", /* Ballarat airport */ + "94863", /* Sheoaks */ + "94866"}; /* Melbourne airport */ + +const int station_count = sizeof (stations) / sizeof (char *); + +/* MySQL stuff */ +MYSQL *mysql; +MYSQL_RES *mysql_result_BoM; +MYSQL_RES *mysql_result_local; +MYSQL_ROW mysql_row_BoM; +MYSQL_ROW mysql_row_local; +char mysql_querytext [1024]; /* build up queries here */ + +/* Command line options */ +int debug; /* -d */ +int verbose; /* -v */ + +/* + * Temperatures for calculation: two pairs of BoM time and temperature, + * and one time and temperature between them for local. + */ +time_t interval_starttime; +float interval_starttemp; +time_t interval_endtime; +float interval_endtemp; +time_t local_time; +float local_temp; +float interval_temp; /* interpolated BoM temperature */ +/* barometric pressure stuff */ +float interval_startpressure; +float interval_endpressure; +float interval_pressure; /* interpolated BoM pressure */ +float local_pressure; + +/* XXX get time zone in here */ +const int my2k = 946731600; /* difference between UNIX and GNU epochs */ +char *startdate_text; /* start date, YYYY-MM-DD */ +char *enddate_text; /* and end date (default: 24 hours later) */ +/* Same times in struct tm format */ +struct tm startdate; +struct tm enddate; +time_t endtime_t; + +void usage (char *myname) +{ + fprintf (stderr, + "Usage: %s YYYY-MM-DD YYYY-MM-DD [-d] [-v] [station-id] [user] [passwd] [host] [db]\n", + myname); + exit (1); +} + +/* + * Make files with comparative data for temperatures. + * + * The remote observations are relatively infrequent, while the local + * observations are frequent. We interpolate the "remote" values linearly to + * estimate the temperature at the time of a local observation, but it doesn't + * make sense to do that for all local observations. Instead, we just take the + * one immediately after the remote observation, then skip to the next remote + * observation. + */ +void mktempcompare (char *stationid) +{ + char filename [PATH_MAX]; + FILE *myfile; /* output file */ + int finishing = 0; /* set to exit loop */ + + /* Create a file name for this comparison */ + strcpy (filename, config.comparefile); + strcat (filename, "-"); + strcat (filename, stationid); + myfile = fopen (filename, "w"); + if (! myfile) + { + syslog (LOG_ERR | LOG_DAEMON, + "Can't open %s: %s (%d)\n", + filename, + strerror (errno), + errno ); + exit (1); + } + chmod (filename, 0666); /* let anybody access it */ + sprintf (mysql_querytext, + "SELECT unix_timestamp(timestamp(date, time)), outside_temp\n" + "FROM remote_observations\n" + "WHERE station_id = '%s'\n" + " AND date >= '%s'\n" + " AND date <= '%s'\n" + "ORDER BY date, time;\n", + stationid, + startdate_text, + enddate_text ); + if (domyquery (mysql_querytext, &mysql_result_BoM)) + exit (1); /* failed and reported */ + if (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM)) /* got something */ + { + interval_starttime = atol (mysql_row_BoM [0]); + interval_starttemp = atof (mysql_row_BoM [1]); + /* Next row for first interval */ + if (! (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM))) /* got something ?*/ + exit (1); /* no */ + interval_endtime = atol (mysql_row_BoM [0]); + interval_endtemp = atof (mysql_row_BoM [1]); + + /* + * Now we have an initial interval. Get the first local record after start + * of interval. + */ + sprintf (mysql_querytext, + "SELECT unix_timestamp(timestamp(date, time)), outside_temp\n" + "FROM %s\n" + "WHERE station_id = '%s'\n" + " AND date >= '%s'\n" + " AND date <= '%s'\n" + "ORDER BY date, time;\n", + config.db_table, + config.station_id, + startdate_text, + enddate_text ); + if (domyquery (mysql_querytext, &mysql_result_local)) + exit (1); /* failed and reported */ + while (mysql_row_local = mysql_fetch_row (mysql_result_local)) /* got something */ + { + local_time = atol (mysql_row_local [0]); + local_temp = atof (mysql_row_local [1]); + if (local_time > interval_starttime) /* we're in business */ + { + while (local_time > interval_endtime) /* beyond current BoM readings */ + { + interval_starttime = interval_endtime; + interval_starttemp = interval_endtemp; + /* Next row for first interval */ + if (! (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM))) /* finished? */ + { + fclose (myfile); + return; + } + interval_endtime = atol (mysql_row_BoM [0]); + interval_endtemp = atof (mysql_row_BoM [1]); + } + + /* + * Now our local reading is between start and end times. Interpolate + * to get BoM temperature at this time. + */ + interval_temp = interval_starttemp + + (interval_endtemp - interval_starttemp) + * (local_time - interval_starttime) / (interval_endtime - interval_starttime); + fprintf (myfile, + "%d %d %2.1f %2.1f %2.1f # %s", + local_time, + local_time - my2k, + local_temp, + interval_temp, + interval_temp - local_temp, + ctime (&local_time) ); + + if (finishing) + { + fclose (myfile); + return; + } + /* Next remote row */ + interval_starttime = interval_endtime; + interval_starttemp = interval_endtemp; + if (! (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM))) /* finished? */ + finishing = 1; + else + { + interval_endtime = atol (mysql_row_BoM [0]); + interval_endtemp = atof (mysql_row_BoM [1]); + } + } + } + } +} + +/* + * Make files with comparative data for barometric pressures. + */ +void mkpresscompare (char *stationid) +{ + char filename [PATH_MAX]; + FILE *myfile; /* output file */ + int finishing = 0; /* set to exit loop */ + + /* Create a file name for this comparison */ + strcpy (filename, config.comparefile); + strcat (filename, "-pressure-"); + strcat (filename, stationid); + myfile = fopen (filename, "w"); + if (! myfile) + { + syslog (LOG_ERR | LOG_DAEMON, + "Can't open %s: %s (%d)\n", + filename, + strerror (errno), + errno ); + exit (1); + } + chmod (filename, 0666); /* let anybody access it */ + sprintf (mysql_querytext, + "SELECT unix_timestamp(timestamp(date, time)), pressure_msl\n" + "FROM remote_observations\n" + "WHERE station_id = '%s'\n" + " AND date >= '%s'\n" + " AND date <= '%s'\n" + " AND pressure_msl IS NOT NULL\n" + "ORDER BY date, time;\n", + stationid, + startdate_text, + enddate_text ); + if (domyquery (mysql_querytext, &mysql_result_BoM)) + exit (1); /* failed and reported */ + if (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM)) /* got something */ + { + interval_starttime = atol (mysql_row_BoM [0]); + interval_startpressure = atof (mysql_row_BoM [1]); + + /* + * Next row for first interval. + * + * The BoM doesn't issue this information very frequently, so we'll accept a + * single reading if we have to. In this case, we just pretend that the + * pressure is constant. + */ + if (! (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM))) /* got something ?*/ + { + finishing = 1; /* no, just do one more result */ + interval_endtime = interval_starttime + 100; + interval_endpressure = interval_startpressure; + } + else + { + interval_endtime = atol (mysql_row_BoM [0]); + interval_endpressure = atof (mysql_row_BoM [1]); + } + + /* + * Now we have an initial interval. Get the first local record after start + * of interval. + */ + sprintf (mysql_querytext, + "SELECT unix_timestamp(timestamp(date, time)), pressure_msl\n" + "FROM %s\n" + "WHERE station_id = '%s'\n" + " AND date >= '%s'\n" + " AND date <= '%s'\n" + " AND pressure_msl IS NOT NULL\n" + "ORDER BY date, time;\n", + config.db_table, + config.station_id, + startdate_text, + enddate_text ); + if (domyquery (mysql_querytext, &mysql_result_local)) + exit (1); /* failed and reported */ + while (mysql_row_local = mysql_fetch_row (mysql_result_local)) /* got something */ + { + local_time = atol (mysql_row_local [0]); + local_pressure = atof (mysql_row_local [1]); + if (local_time > interval_starttime) /* we're in business */ + { + /* + * Unlike with temperatures, we only look at the first pressure reading + * newer than the BoM reading. We have no way of knowing if the BoM has + * similar changes to what we have locally. + */ + while (local_time > interval_endtime) /* beyond current BoM readings */ + { + interval_starttime = interval_endtime; + interval_startpressure = interval_endpressure; + /* Next row for first interval */ + if (! (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM))) /* finished? */ + { + finishing = 1; + interval_endtime += 1000; /* to avoid division by 0 */ + } + else + { + interval_endtime = atol (mysql_row_BoM [0]); + interval_endpressure = atof (mysql_row_BoM [1]); + } + } + + /* + * Now our local reading is between start and end times. Interpolate + * to get BoM pressure at this time. + */ + interval_pressure = interval_startpressure + + (interval_endpressure - interval_startpressure) + * (local_time - interval_starttime) / (interval_endtime - interval_starttime); + fprintf (myfile, + "%d %d %2.1f %2.1f %2.1f # %s", + local_time, + local_time - my2k, + local_pressure, + interval_pressure, + interval_pressure - local_pressure, + ctime (&local_time) ); + local_time = interval_endtime + 1; /* force the next record */ + + if (finishing) + { + fclose (myfile); + return; + } + /* Next remote row */ + interval_starttime = interval_endtime; + interval_starttemp = interval_endtemp; + if (! (mysql_row_BoM = mysql_fetch_row (mysql_result_BoM))) /* finished? */ + finishing = 1; + else + { + interval_endtime = atol (mysql_row_BoM [0]); + interval_endtemp = atof (mysql_row_BoM [1]); + } + } + } + } + fclose (myfile); +} + +int main (int argc, char *argv []) +{ + int station; + /* + * This is tacky. We want to use the standard parameter sequence in + * read_config (), but that doesn't include dates. So we require the sequence + * in usage (). And that means that we need to find a way to specify the end + * day. FIXME. + */ + + if (argc < 3) /* we need at least start date */ + usage (argv [0]); + startdate_text = argv [1]; /* start and */ + enddate_text = argv [1]; /* end date */ + read_config (argc - 2, &argv [2]); /* this implies that we know argv [0] won't be used */ + + mysql = mysql_init (mysql); + if (! mysql) + { + syslog (LOG_ERR | LOG_DAEMON, "Can't initialize MySQL: insufficient memory\n"); + exit (1); + } + if (! mysql_real_connect (mysql, /* DB state */ + config.db_host, /* database host */ + config.db_user, + config.db_passwd, + config.db, + 0, + NULL, + 0 )) + { + syslog (LOG_ERR | LOG_DAEMON, + "Can't connect to databaase: %s (%d)\n", + mysql_error (mysql), + mysql_errno (mysql) ); + exit (1); + } + + /* Generate the files */ + for (station = 0; station < station_count; station++) + { + mktempcompare (stations [station]); + mkpresscompare (stations [station]); + } + exit (0); +} diff -r 000000000000 -r 9dab44dcb331 plots/.1 diff -r 000000000000 -r 9dab44dcb331 plots/.2 diff -r 000000000000 -r 9dab44dcb331 plots/OUTFILE diff -r 000000000000 -r 9dab44dcb331 plots/RCS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/RCS Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +/home/Sysconfig/MasterRCS/home/grog/src/weather/WH-1080/plots/RCS \ No newline at end of file diff -r 000000000000 -r 9dab44dcb331 plots/doplots --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/doplots Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,169 @@ +#!/bin/sh +# Dereel weather: Generate plots of day's activity +# $Id: doplots,v 1.12 2010/02/07 03:24:24 grog Exp $ +# Comment this out to produce all error messages +# SWALLOW="2> /dev/null" +# SWALLOW="/tmp/vomit" +plotset () +{ + COMMON=$BIN/plots/plot-common.gnuplot + INFILE=$BIN/plots/plot-$PLOT.gnuplot + OUTFILE=$HTML/$PLOT-$INTERVAL-$PLOTSIZE.png + +# echo PLOT: $PLOT +# echo GRAPHSIZE: $GRAPHSIZE +# echo INFILE: $INFILE +# echo OUTFILE: $OUTFILE +# echo INTERVAL: $INTERVAL + + cat $COMMON $INFILE | \ + sed "s:MIDNIGHT:$GNUSTART:; s:ENDTIME:$GNUEND:; s:GRAPHFILE:$TEMPFILE:; s:SIZE:$GRAPHSIZE:; s:OUTFILE:$OUTFILE:; s:SMOOTHED: (smoothed):; s:SMOOTH:smooth bezier:; s:XTICS:$XTICS:" \ + | gnuplot $SWALLOW + chmod 666 $OUTFILE + OUTFILE=$HTML/$PLOT-$INTERVAL-$PLOTSIZE-raw.png + cat $COMMON $INFILE | \ + sed "s:MIDNIGHT:$GNUSTART:; s:ENDTIME:$GNUEND:; s:GRAPHFILE:$TEMPFILE:; s:SIZE:$GRAPHSIZE:; s:OUTFILE:$OUTFILE:; s:SMOOTHED::; s:SMOOTH:smooth bezier:; s:XTICS:$XTICS:" \ + | gnuplot $SWALLOW + chmod 666 $OUTFILE +} +PLOTS="humidity temperatures inside-outside-temp inside-temp outside-temp wind pressure rain comparative-pressure comparative-dewpoint comparative-gust comparative-humidity comparative-pressure comparative-temperature comparative-wind comparative-rain BoM-Dereel BoM-Dereel-pressure" + +PATH=$PATH:/usr/local/bin + +export HTML=/home/grog/public_html/weather +BIN=/home/grog/src/weather/WH-1080 + +DATADIR=/var/tmp/ +TEMPFILE=$DATADIR/dereel-weather-plot.$$ + +# Generate some constants. +# Calculate time zone offset from UTC +# Convert to seconds at UTC +NOW=`date +%G%m%d%H%M` +UTC=`TZ=GMT date -j $NOW +%s` +# And in local time zone +LOCAL=`date -j $NOW +%s` +# The difference is the time zone offset. +TZOFFSET=`expr $UTC - $LOCAL` +# Number of seconds in a day +DAYSECS=86400 +# 2000-1-1 0:0:0 UTC, the gnuplot epoch. Or so it should be, but for +# some reason my plots come out offset by 2 hours. Use GNUFUDGE until +# I find out why. +GNUFUDGE=7200 +Y2K=`expr 946684800` +MY2K=`expr $Y2K + $TZOFFSET + $GNUFUDGE` +# End of constant generation + +# Decide what we're going to do. +# Date of plot +if [ "$1" != "" ]; then # parameter supplied + DATE=$1 +else + DATE=`date +%F` +fi + +# Before we forget it, offload the 5 day plot +$BIN/plots/plot5 $DATE +# Which also allows us to calculate midnight +STARTTIME=`date -j -f "%F %T" "$DATE 0:0:0" +%s` +# Adjust to gnu epoch +GNUSTART=`expr $STARTTIME - $Y2K - $TZOFFSET - $GNUFUDGE` +# End of plot. By default, 1 day, unless we have a second parm +ENDDATE=$2 +if [ "$ENDDATE" = "" ]; then # 1 day + INTERVAL=$DATE + GNUEND=`expr $GNUSTART + $DAYSECS` + ENDTIME=`expr $STARTTIME + 86400` + ENDDATE=`date -r $ENDTIME +%F` +else + # midnight on the end day + INTERVAL=$DATE-$2 + ENDTIME=`date -j -f "%F %T" "$2 0:0:0" +%s` + GNUEND=`expr $ENDTIME - $Y2K - $TZOFFSET - $GNUFUDGE` +echo $ENDTIME - $Y2K - $TZOFFSET - $GNUFUDGE +fi +echo end $ENDTIME start $STARTTIME +DURATION=`expr $ENDTIME - $STARTTIME` +echo DURATION $DURATION +# Decide how often to place the time ticks. +if [ $DURATION -eq $DAYSECS ]; then # single day + XTICS=10800 # 3 hours +elif [ $DURATION -eq 172800 ]; then # 2 days + XTICS=21600 # 6 hours +elif [ $DURATION -le 345600 ]; then # up to 4 days + XTICS=43200 # 6 hours +elif [ $DURATION -le 864000 ]; then # up to 10 days + XTICS=86400 # 6 hours +elif [ $DURATION -le 864000 ]; then # up to 10 days + XTICS=86400 # 1 day +elif [ $DURATION -le 2592000 ]; then # up to 30 days + XTICS=172800 # 2 days +else # > 30 days + XTICS=`expr $DURATION / 10` # just hack it +fi + +echo DATE $DATE ENDDATE $ENDDATE INTERVAL $INTERVAL GNUEND $GNUEND DURATION $DURATION + +# Graph sizes +SMALLGRAPH="375,250" +BIGGRAPH="1024, 720" + +# XXX handle correct station + +# Get the readings from the database +# 1: Time +# 2: inside_temp +# 3: outside_temp +# 4: inside_humidity +# 5: outside_humidity +# 6: inside_dewpoint +# 7: outside_dewpoint +# 8: pressure_msl +# 9: wind_speed +# 10: wind_gust +# 11: wind_direction +# 12: rain +QUERY="SELECT unix_timestamp(timestamp(date, time))-$MY2K, inside_temp, outside_temp, inside_humidity, outside_humidity, inside_dewpoint, outside_dewpoint, pressure_msl, wind_speed, wind_gust, wind_direction, rain FROM observations WHERE date >='$DATE' AND date < '$ENDDATE' ORDER BY date, time" +echo $QUERY +echo $QUERY | mysql weather > $TEMPFILE + +# Rainfall +QUERY="SELECT unix_timestamp(timestamp(date, time))-$MY2K, sum(rain) FROM observations WHERE date >='$DATE' AND date < '$ENDDATE' GROUP BY hour(time)" +echo $QUERY +echo $QUERY | mysql weather > $DATADIR/dereel-rainfall + +# echo Today: $STARTTIME `date -r $STARTTIME` + +STATIONS="IVICTORI86 IVICTORI94 IVICTORI102 IVICTORI115 IVICMTHE3 IVICREDA2 94852 94863 94866" +# Comparative graphs +for STATION in $STATIONS; do + QUERY="SELECT unix_timestamp(timestamp(date, time))-$MY2K, outside_temp, outside_dewpoint, outside_humidity, pressure_msl, wind_speed, wind_gust, rain from remote_observations WHERE station_id = '$STATION' and date >='$DATE' AND date < '$ENDDATE' ORDER by date, time;" + echo $QUERY | mysql weather > $DATADIR/weather-readings.$STATION +done +# XXX remove this +# Local readings +QUERY="SELECT unix_timestamp(timestamp(date, time))-$MY2K, outside_temp, outside_dewpoint, outside_humidity, pressure_msl, wind_speed, wind_gust, rain from observations WHERE date >='$DATE' AND date < '$ENDDATE' ORDER by date, time;" +echo $QUERY +echo $QUERY | mysql weather > $DATADIR/weather-readings.IVICTORI124 + +echo $BIN/local-compare $DATE $ENDDATE Dereel +$BIN/local-compare $DATE $ENDDATE Dereel + +# small graphs +GRAPHSIZE=$SMALLGRAPH +PLOTSIZE=small +for PLOT in $PLOTS; do + plotset +done +# big graphs +GRAPHSIZE=$BIGGRAPH +PLOTSIZE=big +for PLOT in $PLOTS; do + plotset +done + +rm $TEMPFILE # $DATADIR/dereel-rainfall +for STATION in $STATIONS; do + rm -f $DATADIR/weather-readings.$STATION +done diff -r 000000000000 -r 9dab44dcb331 plots/myplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/myplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,91 @@ +#!/bin/sh +# +# $Id: myplot,v 1.2 2010/01/10 05:06:54 grog Exp $ +# +# Kludge page to generate plots +# These may need changing +STATION=Dereel +TABLE=observations +STARTDATE="2009-12-23" +ENDDATE="2009-12-24" +COMMON=/home/grog/src/weather/WH-1080/plots/plot-common.gnuplot +GRAPHFILE=/var/tmp/myplot.foo +OUTFILE=/home/grog/public_html/weather/myplot.png + +# Generate some constants. +# Calculate time zone offset from UTC +# Convert to seconds at UTC +NOW=`date +%Y%m%d%H%M` +UTC=`TZ=GMT date -j $NOW +%s` +# And in local time zone +LOCAL=`date -j $NOW +%s` +# The difference is the time zone offset. +TZOFFSET=`expr $UTC - $LOCAL` +# Number of seconds in a day +DAYSECS=86400 +# 2000-1-1 0:0:0 UTC, the gnuplot epoch. Or so it should be, but for +# some reason my plots come out offset by 2 hours. Use GNUFUDGE until +# I find out why. +GNUFUDGE=7200 +Y2K=`expr 946684800` +MY2K=`expr $Y2K + $TZOFFSET + $GNUFUDGE` +# End of constant generation + +# Frob these values +STARTTIME=`date -j -f "%F %T" "$STARTDATE 0:0:0" +%s` +# Adjust to gnu epoch +GNUSTART=`expr $STARTTIME - $Y2K - $TZOFFSET - $GNUFUDGE` +# midnight on the end day +ENDTIME=`date -j -f "%F %T" "$ENDDATE 0:0:0" +%s` +GNUEND=`expr $ENDTIME - $Y2K - $TZOFFSET - $GNUFUDGE` +# echo $ENDTIME - $Y2K - $TZOFFSET - $GNUFUDGE + +# echo end $ENDTIME start $STARTTIME +DURATION=`expr $ENDTIME - $STARTTIME` +# echo DURATION $DURATION +# Decide how often to place the time ticks. +if [ $DURATION -eq $DAYSECS ]; then # single day + XTICS=10800 # 3 hours +elif [ $DURATION -eq 172800 ]; then # 2 days + XTICS=21600 # 6 hours +elif [ $DURATION -le 345600 ]; then # up to 4 days + XTICS=43200 # 6 hours +elif [ $DURATION -le 864000 ]; then # up to 10 days + XTICS=86400 # 6 hours +elif [ $DURATION -le 864000 ]; then # up to 10 days + XTICS=86400 # 1 day +elif [ $DURATION -le 2592000 ]; then # up to 30 days + XTICS=172800 # 2 days +else # > 30 days + XTICS=`expr $DURATION / 10` # just hack it +fi + +# echo DATE $STARTDATE ENDDATE $ENDDATE GNUEND $GNUEND DURATION $DURATION XTICS $XTICS + +# ************************************************** +# End of setup crap +# ************************************************** + +# Do the query +MY2KP1=`expr $Y2K + $TZOFFSET + $GNUFUDGE - $DAYSECS` +MY2KM1=`expr $Y2K + $TZOFFSET + $GNUFUDGE + $DAYSECS` +echo "SELECT unix_timestamp(timestamp(date, time))-$MY2K, outside_temp from $TABLE WHERE station_id = '$STATION' and date >='$STARTDATE' AND date <= '$STARTDATE' ORDER by date, time;" | \ + mysql weather > $GRAPHFILE.1 +echo "SELECT unix_timestamp(timestamp(date, time))-$MY2KM1, outside_temp from $TABLE WHERE station_id = '$STATION' and date >='$ENDDATE' AND date <= '$ENDDATE' ORDER by date, time;" | \ + mysql weather > $GRAPHFILE.2 + +# And the plot + +for GRAPHSIZE in "375,250" "1024, 720"; do +OUTFILE=/home/grog/public_html/weather/myplot.$GRAPHSIZE.png + +( cat $COMMON +cat << EOF + set title "Temperature comparison" + set ylabel "Temperature (°C)" +plot "GRAPHFILE.1" using 1:2 title "23 December" with lines, \ + "GRAPHFILE.2" using 1:2 title "24 December" with lines +EOF +) | sed "s:MIDNIGHT:$GNUSTART:; s:ENDTIME:$GNUEND:; s:GRAPHFILE:$GRAPHFILE:g; s:SIZE:$GRAPHSIZE:; s:OUTFILE:$OUTFILE:; s:SMOOTHED: (smoothed):; s:SMOOTH:smooth bezier:; s:XTICS:$XTICS:" | \ + gnuplot +done diff -r 000000000000 -r 9dab44dcb331 plots/plot-BoM-Dereel-pressure.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-BoM-Dereel-pressure.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ + set title "BoM pressures relative to Dereel" + set ylabel "Pressure (hPa)" +plot "/home/grog/public_html/weather/BoM-compare-pressure-94852" using 2:5 title "Ballarat airport" with lines , \ + "/home/grog/public_html/weather/BoM-compare-pressure-94866" using 2:5 title "Melbourne airport" with lines , \ + "/home/grog/public_html/weather/BoM-compare-pressure-94863" using 2:5 title "Sheoaks" with lines diff -r 000000000000 -r 9dab44dcb331 plots/plot-BoM-Dereel.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-BoM-Dereel.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ + set title "BoM temperatures relative to Dereel" + set ylabel "Temperature (°C)" +plot "/home/grog/public_html/weather/BoM-compare-94852" using 2:5 title "Ballarat airport" with lines SMOOTH, \ + "/home/grog/public_html/weather/BoM-compare-94866" using 2:5 title "Melbourne airport" with lines SMOOTH, \ + "/home/grog/public_html/weather/BoM-compare-94863" using 2:5 title "Sheoaks" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-common.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-common.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,8 @@ + set term png size SIZE small + set output "OUTFILE" + set grid + set xlabel "Time" offset 0,-0.8 + set xdata time + set timefmt "%s" + set xrange ["MIDNIGHT":"ENDTIME"] + set xtics XTICS diff -r 000000000000 -r 9dab44dcb331 plots/plot-common2.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-common2.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ + set grid + set xlabel "Time" offset 0,-0.8 + set xdata time + set timefmt "%s" + set xtics XTICS diff -r 000000000000 -r 9dab44dcb331 plots/plot-common3.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-common3.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,8 @@ + set term png size SIZE small + set output "OUTFILE" + set grid + set xlabel "Time" offset 0,-0.8 + set xdata time + set timefmt "%s" + set format x "%H:%M" + set xtics XTICS diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-dewpoint.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-dewpoint.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,12 @@ + set title "Comparative dew point temperature" + set ylabel "Dew point temperature (°C)" +plot "/var/tmp/weather-readings.94852" using 1:3 title "Ballarat airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94866" using 1:3 title "Melbourne airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94863" using 1:3 title "Sheoaks" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICMTHE3" using 1:3 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:3 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:3 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:3 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:3 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:3 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:3 title "Dereel" with lines lw 2 SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-gust.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-gust.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,12 @@ + set title "Comparative wind gust speed" + set ylabel "Wind speed (km/h)" +plot "/var/tmp/weather-readings.94852" using 1:7 title "Ballarat airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94866" using 1:7 title "Melbourne airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94863" using 1:7 title "Sheoaks" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICMTHE3" using 1:7 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:7 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:7 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:7 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:7 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:7 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:7 title "Dereel" with lines lw 2 SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-humidity.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-humidity.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,12 @@ + set title "Comparative humidity" + set ylabel "Humidity (%)" +plot "/var/tmp/weather-readings.94852" using 1:4 title "Ballarat airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94866" using 1:4 title "Melbourne airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94863" using 1:4 title "Sheoaks" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICMTHE3" using 1:4 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:4 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:4 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:4 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:4 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:4 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:4 title "Dereel" with lines lw 2 SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-pressure.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-pressure.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,12 @@ + set title "Comparative barometric pressure" + set ylabel "Barometric pressure (hPa)" +plot "/var/tmp/weather-readings.94852" using 1:5 title "Ballarat airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94866" using 1:5 title "Melbourne airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94863" using 1:5 title "Sheoaks" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICMTHE3" using 1:5 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:5 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:5 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:5 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:5 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:5 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:5 title "Dereel" with lines lw 2 SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-rain.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-rain.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,13 @@ + set title "Comparative rainfall" + set ylabel "Rainfall (mm)" +plot "/var/tmp/weather-readings.IVICMTHE3" using 1:8 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:8 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:8 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:8 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:8 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:8 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:8 title "Dereel" with lines lw 2 SMOOTH + +# We really want these, but they're currently in a different format (cumulative since 09:00) +# "/var/tmp/weather-readings.94852" using 1:8 title "Ballarat airport" with lines lw 2 SMOOTH, \ +# "/var/tmp/weather-readings.94863" using 1:8 title "Sheoaks" with lines lw 2 SMOOTH, \ diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-temperature.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-temperature.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,12 @@ + set title "Comparative temperature" + set ylabel "Outside temperature (°C)" +plot "/var/tmp/weather-readings.94852" using 1:2 title "Ballarat airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94866" using 1:2 title "Melbourne airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94863" using 1:2 title "Sheoaks" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICMTHE3" using 1:2 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:2 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:2 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:2 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:2 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:2 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:2 title "Dereel" with lines lw 2 SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-comparative-wind.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-comparative-wind.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,12 @@ + set title "Comparative wind speed" + set ylabel "Wind speed (km/h)" +plot "/var/tmp/weather-readings.94852" using 1:6 title "Ballarat airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94866" using 1:6 title "Melbourne airport" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.94863" using 1:6 title "Sheoaks" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICMTHE3" using 1:6 title "Mount Helen" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI86" using 1:6 title "Teesdale" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI94" using 1:6 title "Delacombe" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI102" using 1:6 title "Bacchus Marsh" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI115" using 1:6 title "Buninyong" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICREDA2" using 1:6 title "Redan" with lines lw 2 SMOOTH, \ + "/var/tmp/weather-readings.IVICTORI124" using 1:6 title "Dereel" with lines lw 2 SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-humidity.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-humidity.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ + set ylabel "Humidity (%)" + set title "Relative Humidity" + set yrange [0:100] +plot "GRAPHFILE" using 1:4 title "Inside" with lines SMOOTH, \ + "GRAPHFILE" using 1:5 title "Outside" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-inside-outside-temp.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-inside-outside-temp.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,6 @@ + set title "Inside and outside temperature" + set ylabel "Temperature (°C)" + set yrange [-5:50] + set xrange ["MIDNIGHT":"ENDTIME"] +plot "GRAPHFILE" using 1:2 title "Inside" with lines SMOOTH, \ + "GRAPHFILE" using 1:3 title "Outside" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-inside-temp.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-inside-temp.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ + set title "Inside temperature and dewpoint" + set ylabel "Temperature (°C)" + set yrange [-5:50] +plot "GRAPHFILE" using 1:2 title "Temperature" with lines SMOOTH, \ + "GRAPHFILE" using 1:6 title "Dew point" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-outside-temp.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-outside-temp.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,5 @@ + set title "Outside temperature and dewpoint" + set ylabel "Temperature (°C)" + set yrange [-5:50] +plot "GRAPHFILE" using 1:3 title "Temperature" with lines SMOOTH, \ + "GRAPHFILE" using 1:7 title "Dew point" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-pressure.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-pressure.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,4 @@ + set title "Barometric pressure, SMOOTHED" + set ylabel "Barometric pressure (hPa)" + set yrange [990:1030] +plot "GRAPHFILE" using 1:8 title "Pressure" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-rain.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-rain.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,6 @@ + set title "Rainfall" + set ylabel "Rainfall (mm/hour)" +# set yrange [-5:50] +plot "/var/tmp/dereel-rainfall" using 1:2 title "Hourly rain" with boxes fs solid, \ + "/var/tmp/dereel-rainfall" using 1:2 title "Average rainfall" with lines SMOOTH + diff -r 000000000000 -r 9dab44dcb331 plots/plot-temperatures.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-temperatures.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,7 @@ + set title "Temperature and dewpoint" + set ylabel "Temperature (°C)" + set yrange [-5:50] +plot "GRAPHFILE" using 1:3 title "Outside temperature" with lines SMOOTH, \ + "GRAPHFILE" using 1:7 title "Outside dew point" with lines SMOOTH, \ + "GRAPHFILE" using 1:2 title "Inside temperature" with lines SMOOTH, \ + "GRAPHFILE" using 1:6 title "Inside dew point" with lines SMOOTH diff -r 000000000000 -r 9dab44dcb331 plots/plot-wind.gnuplot --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot-wind.gnuplot Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,6 @@ + set title "Wind speed, SMOOTHED" + set ylabel "Speed (km/h)" +# plot "GRAPHFILE" using 1:9 title "Wind speed" with points, \ +# "GRAPHFILE" using 1:10 title "Gust speed" with points, +plot "GRAPHFILE" using 1:9 title "Wind speed" SMOOTH with lines, \ + "GRAPHFILE" using 1:10 title "Gust speed" SMOOTH with lines diff -r 000000000000 -r 9dab44dcb331 plots/plot5 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plots/plot5 Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,106 @@ +#!/usr/local/bin/bash +# +# $Id: plot5,v 1.1 2010/01/10 06:13:52 grog Exp grog $ +# +# Generate comparative temperature plots for 5 consecutive days. +# These may need changing +STATION=Dereel +TABLE=observations +COMMON=/home/grog/src/weather/WH-1080/plots/plot-common3.gnuplot +GRAPHFILE=/var/tmp/myplot.foo +OUTFILE=/home/grog/public_html/weather/myplot.png + +# Generate some constants. +# Calculate time zone offset from UTC +# Convert to seconds at UTC +NOW=`date +%Y%m%d%H%M` +UTC=`TZ=GMT date -j $NOW +%s` +# And in local time zone +LOCAL=`date -j $NOW +%s` +# The difference is the time zone offset. +TZOFFSET=`expr $UTC - $LOCAL` +# Number of seconds in a day +DAYSECS=86400 +# 2000-1-1 0:0:0 UTC, the gnuplot epoch. Or so it should be, but for +# some reason my plots come out offset by 2 hours. Use GNUFUDGE until +# I find out why. +GNUFUDGE=7200 +Y2K=`expr 946684800` +MY2K=`expr $Y2K + $TZOFFSET + $GNUFUDGE` +# End of constant generation + +# Get the last date for this graph. +if [ $# -ge 1 ]; then + ENDDATE=$1 +else + ENDDATE=`date +%Y-%m-%d` +fi +# echo S $ENDDATE +# Frob these values +ENDTIME=`date -j -f "%F %T" "$ENDDATE 0:0:0" +%s` +# start 4 days earlier +STARTTIME=`expr $ENDTIME - $DAYSECS \* 4` +# Adjust to gnu epoch +GNUSTART=`expr $STARTTIME - $Y2K - $TZOFFSET - $GNUFUDGE` +# echo $ENDTIME - $Y2K - $TZOFFSET - $GNUFUDGE + +# echo end $ENDTIME start $STARTTIME +XTICS=10800 # tics every 3 hours + +# echo DATE $STARTDATE ENDDATE $ENDDATE GNUEND $GNUEND DURATION $DURATION XTICS $XTICS + +# ************************************************** +# End of setup crap +# ************************************************** + +# Do the query +# Offset of day from FOO +OFFSET=`expr $Y2K + $TZOFFSET + $GNUFUDGE` +# echo O $OFFSET S $STARTTIME +for i in 1 2 3 4 5; do + DAY=`date -r $STARTTIME +"%Y-%m-%d"` + DAY[$i]=`date -r $STARTTIME +"%e %b"` +# echo i $i DAY $DAY + echo "SELECT unix_timestamp(timestamp(date, time))-$OFFSET, outside_temp from $TABLE WHERE station_id = '$STATION' and date ='$DAY' ORDER by time;" | \ + mysql weather > $GRAPHFILE.$i + STARTTIME=`expr $STARTTIME + $DAYSECS` # next day + OFFSET=`expr $OFFSET + $DAYSECS` # next day +done + +# And the plot + +GRAPHSIZE="375,250" +OUTFILE=$HTML/5days-$ENDDATE-small.png +( cat $COMMON +cat << EOF + set title "Daily temperature comparison" + set ylabel "Temperature (°C)" +plot "GRAPHFILE.1" using 1:2 title "${DAY[1]}" with lines smooth bezier, \ + "GRAPHFILE.2" using 1:2 title "${DAY[2]}" with lines smooth bezier, \ + "GRAPHFILE.3" using 1:2 title "${DAY[3]}" with lines smooth bezier, \ + "GRAPHFILE.4" using 1:2 title "${DAY[4]}" with lines smooth bezier, \ + "GRAPHFILE.5" using 1:2 title "${DAY[5]}" with lines smooth bezier +EOF +) | sed "s:MIDNIGHT:$GNUSTART:; s:ENDTIME:$GNUEND:; s:GRAPHFILE:$GRAPHFILE:g; s:SIZE:$GRAPHSIZE:; s:OUTFILE:$OUTFILE:; s:SMOOTHED: (smoothed):; s:SMOOTH:smooth bezier:; s:XTICS:$XTICS:" | \ + gnuplot +chmod 666 $OUTFILE + +GRAPHSIZE="1024,720" +OUTFILE=$HTML/5days-$ENDDATE-big.png +( cat $COMMON +cat << EOF + set title "Daily temperature comparison" + set ylabel "Temperature (°C)" +plot "GRAPHFILE.1" using 1:2 title "${DAY[1]}" with lines smooth bezier, \ + "GRAPHFILE.2" using 1:2 title "${DAY[2]}" with lines smooth bezier, \ + "GRAPHFILE.3" using 1:2 title "${DAY[3]}" with lines smooth bezier, \ + "GRAPHFILE.4" using 1:2 title "${DAY[4]}" with lines smooth bezier, \ + "GRAPHFILE.5" using 1:2 title "${DAY[5]}" with lines smooth bezier +EOF +) | sed "s:MIDNIGHT:$GNUSTART:; s:ENDTIME:$GNUEND:; s:GRAPHFILE:$GRAPHFILE:g; s:SIZE:$GRAPHSIZE:; s:OUTFILE:$OUTFILE:; s:SMOOTHED: (smoothed):; s:SMOOTH:smooth bezier:; s:XTICS:$XTICS:" | \ + gnuplot +chmod 666 $OUTFILE + +for i in 1 2 3 4 5; do + rm $GRAPHFILE.$i +done diff -r 000000000000 -r 9dab44dcb331 util.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,100 @@ +/* + * WH-1080 data input utility. + * Utility functions + * + * Greg Lehey, 22 November 2009 + * + * $Id: util.c,v 1.6 2009/12/18 02:13:23 grog Exp $ + */ + +#include "wh1080.h" + +/* Used in many places, but not here */ +char *wind_directions [] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW "}; + +float print_previous_rain; /* counter for print_readings */ + +/* Print out readings */ +void print_readings (struct readings *readings) +{ + static int lines = 0; + char timestamp [STAMPSIZE]; + + if (lines == 0) + printf ("\n" + " Page sv IT IH IDP OT OH ODP hPa SL abs rel Wind Gust Dir Rain CR PR \n"); + + strftime (timestamp, STAMPSIZE, "%F %T", localtime (&readings->timestamp)); /* format time and date */ + + printf ("%17s %5x %3d %5.1f %3d %5.1f %5.1f %3d %5.1f %6.1f %6.1f %6.1f %6.1f %5.1f %5.1f %3s %4.1f %4.1f %4.1f\n", + timestamp, /* current time */ + readings->page, /* machine page number */ + readings->last_save_mins, /* last save minutes (?) */ + readings->inside_temp, /* inside temperature */ + readings->inside_humidity, /* humidity in percent */ + readings->inside_dewpoint, /* inside dewpoint */ + readings->outside_temp, /* outside temperature */ + readings->outside_humidity, /* humidity in percent */ + readings->outside_dewpoint, /* outside dewpoint */ + readings->pressure, /* absolute pressure, hPa */ + readings->pressure_sea_level, /* sea level pressure, hPa */ + readings->page1_abs_pressure, /* absolute pressure, hPa */ + readings->page1_rel_pressure, /* relative pressure, hPa */ + readings->wind_speed, /* wind speed in km/h */ + readings->wind_gust, /* wind gust speed in km/h */ + readings->wind_direction_text, /* wind direction */ + readings->rain - print_previous_rain, /* delta rainfall in mm */ + readings->rain, /* current rainfall in mm */ + readings->previous_rain ); /* previous rainfall in mm */ + print_previous_rain = readings->rain; /* reported this far */ + if (++lines > 60) + lines = 0; +} + +/* + * Calculate dewpoint temperature. Replace this with something with better references. + */ +float dewpoint (float temp, int humidity) +{ + float Es; /* saturated water vapour pressure at dry-bulb temperature */ + float Ep; /* partial pressure at this humidity */ + + Es = 6.11 * pow (10.0, 7.5 * (temp / (237.7 + temp))); + Ep = (humidity * Es) / 100; + return (-430.22 + 237.7 * log (Ep)) / (-log (Ep) + 19.08); +} + +/* + * Calculate inside and outside dewpoints and store in the record. + */ +void set_dewpoints (struct readings *readings) +{ + readings->inside_dewpoint = dewpoint (readings->inside_temp, readings->inside_humidity); + readings->outside_dewpoint = dewpoint (readings->outside_temp, readings->outside_humidity); +} + +/* + * Convert pressure to sea-level pressure. The barometer is in the inside unit, + * so we use the internal temperature as a base. + */ +void set_sea_level_pressure (struct readings *readings) +{ + float e = - config.elevation / (KELVIN (readings->inside_temp) * 29.263); + + e = exp (e); + + readings->pressure_sea_level = readings->pressure / e; +} + +/* + * Simplified date conversion. + * text must be big enough for format; we don't check. + */ +void datetext (time_t date, char *text, char *format) +{ + struct tm *date_tm; /* struct tm format of date */ + + date_tm = localtime (&date); /* and in struct tm format */ + strftime (text, 64, format, date_tm); +} diff -r 000000000000 -r 9dab44dcb331 web/RCS --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/RCS Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,1 @@ +/home/Sysconfig/MasterRCS/home/grog/src/weather/WH-1080/web/RCS \ No newline at end of file diff -r 000000000000 -r 9dab44dcb331 web/comparison.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/comparison.php Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + +
+

+ This page shows comparative readings from a number of weather stations. It's still + experimental. This compares data from the following weather stations in , and also Melbourne airport, a little further away ( doesn't report wind). +

+ +
    +
  • + (currently offline). +
  • +
  • + airport () +
  • +
  • + +
  • +
  • + +
  • +
  • + (this station) +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + () +
  • + +
  • + +
  • +
  • + () +
  • +
+ + + + + +EOS; + } + else /* set edit */ + { + $raw = ""; + print <<< EOS +
+ +
+ +EOS; + } +?> +
+ +
+ + +
+
+ + diff -r 000000000000 -r 9dab44dcb331 web/db.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/db.php Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,374 @@ + + + + + + + + + + + + + + + +
+

+ This is an experimental page that I'm working on as part of my weather reporting software. + It'll grow over time. In the meantime, you can get more complete version of this + information from the for this station. +

+ + + Invalid date: $mydate. Using today's date +

+EOS; + $date = date ("Y-m-d"); + } + } + else + { + $mydate = getdate (); + $date = date ("Y-m-d"); + } + + $istoday = $date == date ("Y-m-d"); /* if today, get current readings */ + + /* Environment to check for graphs */ + $yesterday = formatdate ("Ymd", (addsecs ($mydate, -86400))); + $tomorrow = formatdate ("Ymd", (addsecs ($mydate, 86400))); + + + print <<< EOS + + + + + + + +EOS; + + if (! $istoday) + print <<< EOS + + + + +EOS; + print <<< EOS + + +
+
+ + +
+
+
+ + + + + + + +
+ + + +
+
+
+
+ + +
+
+
+ +
+
+ +EOS; + + /* Set up database stuff */ + /* XXX This stuff should come from config */ + require "db.inc"; + $hostname = "localhost"; + $username = "grog"; + $password = ""; + $database = "weather"; + $dbtable = "observations"; + $station_id = "Dereel"; + + /* Connect to the server */ + if (! ($connection = @ mysql_pconnect ($hostname, $username, $password))) + showerror (); + + if (! mysql_select_db ($database, $connection)) + showerror (); + + if ($istoday) + { + $now = time (); + $start = time () - 600; /* 5 minutes ago */ + + $wind_directions = array ("N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"); + $result = mysql_query (<<< EOS +SELECT AVG(inside_humidity), + AVG(inside_temp), + AVG(inside_dewpoint), + AVG(outside_humidity), + AVG(outside_temp), + AVG(outside_dewpoint), + AVG(pressure_msl), + AVG(wind_speed), + AVG(wind_gust), + AVG(wind_direction), + SUM(rain) +FROM $dbtable +WHERE unix_timestamp(timestamp(date,time)) >= $start + AND unix_timestamp(timestamp(date, time)) <= $now + AND station_id = "$station_id" +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $inside_humidity = sprintf ("%2.0f", $row [0]); + $inside_temperature = sprintf ("%2.1f", $row [1]); + $inside_dewpoint = sprintf ("%2.1f", $row [2]); + $outside_humidity = sprintf ("%2.1f", $row [3]); + $outside_temperature = sprintf ("%2.1f", $row [4]); + $outside_dewpoint = sprintf ("%2.1f", $row [5]); + $pressure_msl = sprintf ("%2.1f", $row [6]); + $wind_speed = sprintf ("%2.1f", $row [7]); + $wind_gust = sprintf ("%2.1f", $row [8]); + $wind_direction = sprintf ("%2.1f", $row [9]); + $rain = sprintf ("%2.1f", $row [10]); + $wind_direction_text = $wind_directions [($row [9] + 11.25) / 22.5]; + } + } + + $result = mysql_query (<<< EOS +SELECT @max_inside_humidity := MAX(inside_humidity), + @min_inside_humidity := MIN(inside_humidity), + @max_inside_temp := MAX(inside_temp), + @min_inside_temp := MIN(inside_temp), + @max_inside_dewpoint := MAX(inside_dewpoint), + @min_inside_dewpoint := MIN(inside_dewpoint), + @max_outside_humidity := MAX(outside_humidity), + @min_outside_humidity := MIN(outside_humidity), + @max_outside_temp := MAX(outside_temp), + @min_outside_temp := MIN(outside_temp), + @max_outside_dewpoint := MAX(outside_dewpoint), + @min_outside_dewpoint := MIN(outside_dewpoint), + @max_pressure_msl := MAX(pressure_msl), + @min_pressure_msl := MIN(pressure_msl), + @max_wind_speed := MAX(wind_speed), + @min_wind_speed := MIN(wind_speed), + @max_wind_gust := MAX(wind_gust), + @min_wind_gust := MIN(wind_gust), + SUM(rain) +FROM $dbtable +WHERE date = "$date" + AND station_id = "$station_id" +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $max_inside_humidity = sprintf ("%2.1f", $row [0]); + $min_inside_humidity = sprintf ("%2.1f", $row [1]); + $max_inside_temp = sprintf ("%2.1f", $row [2]); + $min_inside_temp = sprintf ("%2.1f", $row [3]); + $max_inside_dewpoint = sprintf ("%2.1f", $row [4]); + $min_inside_dewpoint = sprintf ("%2.1f", $row [5]); + $max_outside_humidity = sprintf ("%2.1f", $row [6]); + $min_outside_humidity = sprintf ("%2.1f", $row [7]); + $max_outside_temp = sprintf ("%2.1f", $row [8]); + $min_outside_temp = sprintf ("%2.1f", $row [9]); + $max_outside_dewpoint = sprintf ("%2.1f", $row [10]); + $min_outside_dewpoint = sprintf ("%2.1f", $row [11]); + $max_pressure_msl = sprintf ("%2.1f", $row [12]); + $min_pressure_msl = sprintf ("%2.1f", $row [13]); + $max_wind_speed = sprintf ("%2.1f", $row [14]); + $min_wind_speed = sprintf ("%2.1f", $row [15]); + $max_wind_gust = sprintf ("%2.1f", $row [16]); + $min_wind_gust = sprintf ("%2.1f", $row [17]); + $sum_rain = sprintf ("%2.1f", $row [18]); + } + + $vars = array ("inside_humidity", + "inside_temp", + "inside_dewpoint", + "outside_humidity", + "outside_temp", + "outside_dewpoint", + "pressure_msl", + "wind_speed", + "wind_gust"); + foreach ($vars as $var) + { + $result = mysql_query (<<< EOS +SELECT time from $dbtable +WHERE date = "$date" + AND station_id = "$station_id" + AND $var = @max_$var +LIMIT 1 +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $max = "max_{$var}_time"; + $$max = $row [0]; + } + else + { + print <<< EOS +

+ No data found for $date. +

+ + +EOS; + exit; + } + $result = mysql_query (<<< EOS +SELECT time from $dbtable +WHERE date = "$date" + AND station_id = "$station_id" + AND $var = @min_$var +LIMIT 1 +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $min = "min_{$var}_time"; + $$min = $row [0]; + } + } +} + ?> + +

+ + +

+ + +
+ +

+ Graphs for +

+ /dev/null >/dev/null"); + ?> +
+ + +
+
+ + diff -r 000000000000 -r 9dab44dcb331 web/db2.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/db2.php Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,422 @@ + + + + + + + + + + + + + + + +
+

+ This is an experimental page that I'm working on as part of my weather reporting software. + It'll grow over time. In the meantime, you can get more complete version of this + information from the for this station. +

+ + + Invalid date: $mydate. Using today's date +

+EOS; + $startdate = date ("Y-m-d"); + } + } + else + { + $mydate = getdate (); + $startdate = date ("Y-m-d"); + } + + $oneday = 1; /* Readings for a single day until proven otherwise */ + if (array_key_exists ("enddate", $_GET)) + { + $myenddate = validdate ($_GET ["enddate"], "date"); + if (is_array ($myenddate)) /* valid date */ + { + $oneday = 0; /* Multidate XXX check more carefully */ + $enddate = formatdate ("Y-m-d", $myenddate); + } + else + { + $enddate = date ("Y-m-d", addsecs ($mydate, 86400)); + print <<< EOS +

+ Invalid date: $myenddate. Using $enddate +

+EOS; + } + } + else + { + $myenddate = addsecs ($mydate, 86400); + $enddate = formatdate ("Y-m-d", $myenddate); + } + + /* + * If today, get current readings. In this case, we ignore enddate, since it + * can't mean anything. */ + $istoday = $startdate == date ("Y-m-d"); + + /* Environment to check for graphs */ + $yesterday = formatdate ("Ymd", (addsecs ($mydate, -86400))); + $tomorrow = formatdate ("Ymd", (addsecs ($mydate, 86400))); + + + print <<< EOS + + + + + + + +EOS; + + if (! $istoday) + print <<< EOS + + + + +EOS; + print <<< EOS + + +
+
+ + +
+
+
+ + + + + + + +
+ + + +
+
+
+
+ + +
+
+
+ +
+
+ +EOS; + + /* Set up database stuff */ + /* XXX This stuff should come from config */ + require "db.inc"; + $hostname = "localhost"; + $username = "grog"; + $password = ""; + $database = "weather"; + $dbtable = "observations"; + $station_id = "Dereel"; + + /* Connect to the server */ + if (! ($connection = @ mysql_pconnect ($hostname, $username, $password))) + showerror (); + + if (! mysql_select_db ($database, $connection)) + showerror (); + + if ($istoday) + /* get current readings. These are really the average of the last 5 minutes */ + { + $now = time (); + $start = time () - 600; /* 5 minutes ago */ + + $wind_directions = array ("N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"); + $result = mysql_query (<<< EOS +SELECT AVG(inside_humidity), + AVG(inside_temp), + AVG(inside_dewpoint), + AVG(outside_humidity), + AVG(outside_temp), + AVG(outside_dewpoint), + AVG(pressure_msl), + AVG(wind_speed), + AVG(wind_gust), + AVG(wind_direction), + SUM(rain) +FROM $dbtable +WHERE unix_timestamp(timestamp(date,time)) >= $start + AND unix_timestamp(timestamp(date, time)) <= $now + AND station_id = "$station_id" +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $inside_humidity = sprintf ("%2.0f", $row [0]); + $inside_temperature = sprintf ("%2.1f", $row [1]); + $inside_dewpoint = sprintf ("%2.1f", $row [2]); + $outside_humidity = sprintf ("%2.1f", $row [3]); + $outside_temperature = sprintf ("%2.1f", $row [4]); + $outside_dewpoint = sprintf ("%2.1f", $row [5]); + $pressure_msl = sprintf ("%2.1f", $row [6]); + $wind_speed = sprintf ("%2.1f", $row [7]); + $wind_gust = sprintf ("%2.1f", $row [8]); + $wind_direction = sprintf ("%2.1f", $row [9]); + $rain = sprintf ("%2.1f", $row [10]); + $wind_direction_text = $wind_directions [($row [9] + 11.25) / 22.5]; + } + } + + $result = mysql_query (<<< EOS +SELECT @max_inside_humidity := MAX(inside_humidity), + @min_inside_humidity := MIN(inside_humidity), + @max_inside_temp := MAX(inside_temp), + @min_inside_temp := MIN(inside_temp), + @max_inside_dewpoint := MAX(inside_dewpoint), + @min_inside_dewpoint := MIN(inside_dewpoint), + @max_outside_humidity := MAX(outside_humidity), + @min_outside_humidity := MIN(outside_humidity), + @max_outside_temp := MAX(outside_temp), + @min_outside_temp := MIN(outside_temp), + @max_outside_dewpoint := MAX(outside_dewpoint), + @min_outside_dewpoint := MIN(outside_dewpoint), + @max_pressure_msl := MAX(pressure_msl), + @min_pressure_msl := MIN(pressure_msl), + @max_wind_speed := MAX(wind_speed), + @min_wind_speed := MIN(wind_speed), + @max_wind_gust := MAX(wind_gust), + @min_wind_gust := MIN(wind_gust), + SUM(rain) +FROM $dbtable +WHERE date >= "$startdate" + AND date < "$enddate" + AND station_id = "$station_id" +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $max_inside_humidity = sprintf ("%2.1f", $row [0]); + $min_inside_humidity = sprintf ("%2.1f", $row [1]); + $max_inside_temp = sprintf ("%2.1f", $row [2]); + $min_inside_temp = sprintf ("%2.1f", $row [3]); + $max_inside_dewpoint = sprintf ("%2.1f", $row [4]); + $min_inside_dewpoint = sprintf ("%2.1f", $row [5]); + $max_outside_humidity = sprintf ("%2.1f", $row [6]); + $min_outside_humidity = sprintf ("%2.1f", $row [7]); + $max_outside_temp = sprintf ("%2.1f", $row [8]); + $min_outside_temp = sprintf ("%2.1f", $row [9]); + $max_outside_dewpoint = sprintf ("%2.1f", $row [10]); + $min_outside_dewpoint = sprintf ("%2.1f", $row [11]); + $max_pressure_msl = sprintf ("%2.1f", $row [12]); + $min_pressure_msl = sprintf ("%2.1f", $row [13]); + $max_wind_speed = sprintf ("%2.1f", $row [14]); + $min_wind_speed = sprintf ("%2.1f", $row [15]); + $max_wind_gust = sprintf ("%2.1f", $row [16]); + $min_wind_gust = sprintf ("%2.1f", $row [17]); + $sum_rain = sprintf ("%2.1f", $row [18]); + } + + $vars = array ("inside_humidity", + "inside_temp", + "inside_dewpoint", + "outside_humidity", + "outside_temp", + "outside_dewpoint", + "pressure_msl", + "wind_speed", + "wind_gust"); + foreach ($vars as $var) + { + $result = mysql_query (<<< EOS +SELECT date, time from $dbtable +WHERE date >= "$startdate" + AND date < "$enddate" + AND station_id = "$station_id" + AND $var = @max_$var +LIMIT 1 +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $max = "max_{$var}_time"; + if ($oneday) + $$max = $row [1]; /* omit time */ + else + $$max = $row [0] . " " . $row [1]; + } + else + { + print <<< EOS +

+ No data found for $startdate. +

+ + +EOS; + exit; + } + $result = mysql_query (<<< EOS +SELECT date, time from $dbtable +WHERE date >= "$startdate" + AND date < "$enddate" + AND station_id = "$station_id" + AND $var = @min_$var +LIMIT 1 +EOS + , $connection ); + + if (! $result) + showerror (); + if ($row = mysql_fetch_array ($result, MYSQL_NUM)) + { + $min = "min_{$var}_time"; + if ($oneday) + $$min = $row [1]; /* omit time */ + else + $$min = $row [0] . " " . $row [1]; + } + } +} + ?> + +

+ + +

+ + +
+ +

+ Graphs for +

+ /dev/null >/dev/null"); + else + system ("$doplot $startdate $enddate 2>/dev/null >/dev/null"); + } + ?> +
+ +
+
+ + diff -r 000000000000 -r 9dab44dcb331 web/index.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/index.php Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + +
+

+ This is an experimental page that I'm working on as part of my weather reporting software. + It'll grow over time. In the meantime, you can get more complete version of this + information from the for this station. +

+ +

+ Current readings at +

+ + + +
+ +

+ Readings for +

+ +
+ + +
+ +

+ Yesterday's readings +

+ +
+ + +
+
+ + diff -r 000000000000 -r 9dab44dcb331 wh1080.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wh1080.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,630 @@ +/* + * Data input utility for Fine Offset WH-1080 weather station. + * This bases on code supplied to me by Steve Woodford. + * + * Greg Lehey, 12 November 2009 + * + * $Id: wh1080.c,v 1.18 2010/02/07 03:40:37 grog Exp $ + */ + +#include "wh1080.h" +struct usb_device *station_device; +usb_dev_handle *station; + +/* Data from the device */ +struct wh1080_page0 page0; +struct wh1080_page1 page1; +struct wh1080_readings current_data [2]; /* current data readings, from station */ + +struct readings current_readings; /* current data readings, our version */ +struct readings previous_readings; /* copy of previous set of readings */ + +char previous_wind_direction_text [4]; /* reuse previous direction if we have no wind */ + +int previous_rain; /* previous rainfall reading: we need a difference */ +float db_previous_rain; /* previous rainfall reading for database */ + +struct libusb_context **usb_context; + +#if BYTE_ORDER == BIG_ENDIAN +/* + * The station sends data in big-endian format. If we're running on a + * big-endian machine, we need to turn the 16 bit fields around. + */ + +void reend_page0 () +{ + page0.current_page = le16toh (page0.current_page); +} + +void reend_page1 () +{ + page1.rel_pressure = le16toh (page1.rel_pressure); + page1.abs_pressure = le16toh (page1.abs_pressure); +} + +void reend_readings (struct wh1080_readings *page) +{ + page->inside_temp = le16toh (page->inside_temp); /* inside temperature */ + page->outside_temp = le16toh (page->outside_temp); /* outside temperature */ + page->pressure = le16toh (page->pressure); /* absolute pressure, hPa */ + page->rain = le16toh (page->rain); /* rainfall in 0.3 mm units */ +} +#endif + +/* + * Scan USB busses for our device. + * Return 1 and device information to devp if found. + */ +int find_usb_device (int vendor, int product, struct usb_device **devp) +{ + struct usb_bus *bus; + int count; + + count = usb_find_busses (); + count = usb_find_devices (); + + for (bus = usb_get_busses (); bus; bus = bus->next) + { + for (*devp = bus->devices; *devp; *devp = (*devp)->next) + { + if ((*devp)->descriptor.idVendor == vendor + && (*devp)->descriptor.idProduct == product ) + return 1; + } + } + return 0; /* not found */ +} + +/* + * Set up communications with the weather station. + * On error, print message and die. Return means success. + */ +void device_setup () +{ + usb_init (); /* initialize libusb */ + + if (! find_usb_device (WH1080_USB_VENDOR, WH1080_USB_PRODUCT, &station_device)) + { + fprintf (stderr, "Can't find WH-1080 device\n"); + exit (1); + } + + /* Open station */ + if (! (station = usb_open (station_device))) + { + fprintf (stderr, + "Can't open weather station: %s (%d)\n", + usb_strerror (), + errno); + exit (1); + } + +#ifdef linux + /* + * For some reason, Linux gives a device to the kernel, so we need + * to prise it away again. + */ +#if 0 + http://blemings.org/hugh/blog/blosxom.cgi/2008/01/15#20080115a + struct usb_bus *bus_list; + struct usb_device *dev = NULL; + struct usb_dev_handle *handle; + + + /* Look for the first u4xx device we can find then try and open */ + if((dev = find_u4xx(bus_list)) == NULL) { + return NULL; + } + + /* Try and get a handle to the device */ + if((handle = usb_open(dev)) == NULL) { + return NULL; + } + + /* The kernel's HID driver will seize the USBMicro device as it + says it's a HID device - we need to tell the kernel to + let go of it */ + if (usb_detach_kernel_driver_np(handle, 0) < 0) { + /* If this fails, usually just means that no kernel driver + had attached itself to the device so just ignore/warn */ + } + + /* Set the configuration */ + if(usb_set_configuration(handle, 1) != 0) { + usb_close(handle); + return NULL; + } + + /* Clain interface - gather would need to this for each + interface if the device has more than one */ + if (usb_claim_interface(handle, 0) != 0) { + usb_close(handle); + return NULL; + } + + /* etc. etc. */ + +#endif + if (usb_detach_kernel_driver_np (station, 0) < 0) + { + fprintf (stderr, "%s (%d)\n", usb_strerror (), errno); +/* exit (1); */ + } +#endif + + /* And grab the interface */ + if (usb_claim_interface (station, 0) < 0) + { + fprintf (stderr, "%s (%d)\n", usb_strerror (), errno); + exit (1); + } +} + +/* + * Try to recover from USB breakage. + * XXX This doesn't work in the current form. + */ +void device_reset () +{ + if (usb_release_interface (station, 0) < 0) + fprintf (stderr, + "Can't release interface: %s (%d)\n", + usb_strerror (), + errno); +#if 0 + if (usb_close (station) < 0) + fprintf (stderr, + "Can't close interface: %s (%d)\n", + usb_strerror (), + errno); + device_setup (); +#endif +} + + +/* + * Write control data to device. We don't write real data. + */ +int write_station_control (char *buf) +{ + int written; + char textdate [64]; + + /* XXX Find out what all this means, and be cleverer about retries */ + do + { + if (written = (usb_control_msg (station, + USB_TYPE_CLASS + USB_RECIP_INTERFACE, + 0x9, + 0x200, + 0, + buf, + 8, /* we always write 8 bytes */ + WH1080_WRITE_TIMEOUT) > 0)) + return written; +#if 0 + if (errno == ENOTTY) + { + fprintf (stderr, "USB bus stuck, reinitializing\n"); + device_reset (); + } +#endif + } + while (errno == EINTR); /* || (errno == ENOTTY)); */ + + datetext (time (NULL), textdate, "%e %B %Y %T"); + fprintf (stderr, + "%s: PID %d: can't write to device: %s (%d)\n", + textdate, + getpid (), + usb_strerror (), + errno ); + return 0; +} + +/* + * Read 8 bytes from the device. + */ +int read_station (char *buf) +{ + int bytes_read; + + /* XXX Find out what all this means, and be cleverer about retries */ + do + { + if ((bytes_read = usb_interrupt_read (station, + USB_ENDPOINT_IN | USB_RECIP_INTERFACE, + buf, + 8, + 10 )) == 8 ) + return bytes_read; + if (errno == EAGAIN) + usleep (10000); + } + while (errno != EINTR); + fprintf (stderr, + "Can't read device: %s (%d)\n", + usb_strerror (), + errno ); + return 0; +} + +/* Read a page (32 bytes) from the device. */ +int read_station_page (uint16_t page, char *buf) +{ + int bytes_read = 0; /* keep track of input */ + struct read_page_request + { + char command; /* XXX I'm guessing at this */ + uint16_t address; + char length; + } __attribute__ ((packed)) request [2]; + +#if BYTE_ORDER == LITTLE_ENDIAN + page = htobe16 (page); /* in big-endian for the command */ +#endif + request [0] = (struct read_page_request) {0xa1, page, 32}; + request [1] = request [0]; + if (write_station_control ((char *) request) == 0) + exit (1); /* XXX we've already complained */ + while (bytes_read < 32) + bytes_read += read_station ((char *) &buf [bytes_read]); + return bytes_read; +} + +/* + * Read and compare page from station. Keep trying until we get two that are + * the same. + * + * XXX don't loop for ever. + */ +int read_valid_station_page (uint16_t page, char *buf) +{ + char duplicate [WH1080_PAGE_SIZE]; + + read_station_page (page, duplicate); /* read once for comparison */ + while (1) + { + read_station_page (page, buf); /* and once where we want it */ + if (! memcmp (buf, duplicate, WH1080_PAGE_SIZE)) /* bingo! */ + return WH1080_PAGE_SIZE; + memcpy (duplicate, buf, WH1080_PAGE_SIZE); + } +} + +/* Read observations from specified page. + * + * We have to read 32 bytes from the device, but the data we want is only 16 + * bytes. If it's the last 16 bytes in memory, who knows what will happen? To + * be on the safe side, always read from an even-numbered page and then return 0 + * or 1 to point to the correct entry in (global) current_data. + */ +void read_readings (int page, struct readings *readings) +{ + int pagehalf = (page & 0x10) >> 4; /* index in our two entries */ + +#if 0 + /* Page 1: pressure readings, currently unused */ + read_valid_station_page (WH1080_PAGE1, (char *) &page1); +#if BYTE_ORDER == BIG_ENDIAN +/* reend_page0 (); */ + reend_page1 (); +#endif +#endif + + /* Read the data itself */ + read_valid_station_page (page & WH1080_PAGE_MASK, (char *) ¤t_data); + +#if 0 + printf ("\n"); + hexdump ((unsigned char *) current_data, 2 * sizeof (struct wh1080_readings)); +#endif +#if BYTE_ORDER == BIG_ENDIAN + /* Turn both around to avoid surprises */ + reend_readings (¤t_data [0]); + reend_readings (¤t_data [1]); +#endif + + /* + * Now copy the info back to readings. Note that we don't set the timestamp + * here; only the caller knows when this reading was made. + */ + readings->page = page; /* save page number too */ + readings->last_save_mins + = current_data [pagehalf].last_save_mins; /* last save minutes (?) */ + readings->inside_humidity + = current_data [pagehalf].inside_humidity; /* humidity in percent */ + /* This appears to be an "out of range" situation. */ + if (readings->inside_humidity == 255) + readings->inside_humidity = previous_readings.inside_humidity; + readings->inside_temp + = ((float) current_data [pagehalf].inside_temp) / 10; /* inside temperature */ + readings->outside_humidity + = current_data [pagehalf].outside_humidity; /* humidity in percent */ + if (readings->outside_humidity == 255) + readings->outside_humidity = previous_readings.outside_humidity; + readings->outside_temp + = ((float) current_data [pagehalf].outside_temp) / 10; /* outside temperature */ + readings->pressure + = ((float) current_data [pagehalf].pressure) / 10 /* absolute pressure, hPa */ + - config.pressure_error; /* adjust for inaccuracies */ + readings->wind_speed + = ((float) current_data [pagehalf].wind_speed) / 3.6; /* wind speed in km/h */ + readings->wind_gust + = ((float) current_data [pagehalf].wind_gust) / 3.6; /* wind gust speed in km/h */ + /* + * Wind direction is confusing. It's normally a value between 0 and 15, but + * it's 0x80 if there's no wind at all. Following an idea by Steve Woodford, + * reuse the previous value if it's 0x80. + * + * Other values shouldn't happen. If they do, report them in numeric form. + * + * In addition to the text, we save the original value in degrees for + * Wunderground and friends. If it's invalid, we simply don't send it. + */ + if (current_data [pagehalf].wind_direction == 0x80) /* no wind */ + { + strcpy (readings->wind_direction_text, previous_wind_direction_text); + readings->wind_direction = INVALID_DIRECTION; + } + else if (current_data [pagehalf].wind_direction < 16) /* valid direction */ + { + strcpy (previous_wind_direction_text, wind_directions [current_data [pagehalf].wind_direction]); + strcpy (readings->wind_direction_text, wind_directions [current_data [pagehalf].wind_direction]); + readings->wind_direction = ((float) current_data [pagehalf].wind_direction) * 22.5; + } + else /* invalid value */ + { + sprintf (readings->wind_direction_text, + "%3d", + current_data [pagehalf].wind_direction); /* just put in the number */ + readings->wind_direction = INVALID_DIRECTION; + } + + /* Count incremental rainfall in readings->rain */ + if (current_data [pagehalf].rain != previous_rain) + { + int temprain = current_data [pagehalf].rain - previous_rain; + + if (temprain < 0) /* wraparound */ + temprain += USHRT_MAX; /* add 65536 */ + readings->rain += ((float) temprain) * 0.3; /* rainfall in mm */ + previous_rain = current_data [pagehalf].rain; + } + + set_dewpoints (readings); + set_sea_level_pressure (readings); + + /* Stuff from page 1 */ + readings->page1_abs_pressure = ((float) page1.abs_pressure) / 10; /* absolute pressure, hPa */ +} + +/* + * Read page 0. + */ + +void read_page0 () +{ + /* + * Page 0: magic and offset of current page + */ + do + read_valid_station_page (WH1080_PAGE0, (char *) &page0); + while (((page0.magic != WH1080_PAGE0_MAGIC1A) + && (page0.magic != WH1080_PAGE0_MAGIC1B) ) + || (page0.magic2 != WH1080_PAGE0_MAGIC2) ); +} + +/* + * Get a set of readings from the station. + */ +void read_station_data () +{ + read_page0 (); /* get current page number from page0 */ + read_readings (page0.current_page, ¤t_readings); + current_readings.timestamp = time (NULL); +} + +void dump_memory () +{ + int page; + struct readings readings; + time_t reading_time; /* calculate time of the reading */ + struct tm *reading_tm; /* for trimming off seconds */ + + /* + * Calculate the time of the most recent archive reading, which is + * current_readings.last_save_mins old. This is confused by the fact that the + * times don't include seconds, so for some semblence of uniformity, assume + * that we're in the middle of the minute. + */ + reading_time = time (NULL); /* now */ + reading_tm = localtime (&reading_time); /* and in struct tm format */ + reading_time -= reading_tm->tm_sec; /* adjust to beginning of the minute */ + /* XXX decide how to round */ + + /* + * Note that there's a race condition in the end condition. + * page0.current_page could increment during dumping. That's perfectly + * acceptable. + */ + for (page = page0.current_page + WH1080_ARCHIVE_RECORD_SIZE; + page != page0.current_page; + page += WH1080_ARCHIVE_RECORD_SIZE) + { + if (page >= 0xfff0) /* wrap around */ + page = WH1080_FIRST_ARCHIVE; /* back to last one in memory */ + read_readings (page, &readings); +#if 0 + if (readings.last_save_mins != 30) /* this seems to always be the value in archive readings */ + return; /* this would be the beginning of time? */ +#endif + readings.timestamp = reading_time; + print_readings (&readings); + reading_time += WH1080_ARCHIVE_INTERVAL; + } +} + +void insert_db_row (struct readings *readings) +{ + char reading_date [STAMPSIZE]; /* formatted date */ + char reading_time [STAMPSIZE]; /* and time */ + + strftime (reading_date, STAMPSIZE, "%F", localtime (&readings->timestamp)); /* format time and date */ + strftime (reading_time, STAMPSIZE, "%T", localtime (&readings->timestamp)); /* format time and date */ + + sprintf (mysql_querytext, + "INSERT INTO %s\n" + " (station_id, date, time, inside_humidity, inside_temp, inside_dewpoint,\n" + " outside_humidity, outside_temp, outside_dewpoint, pressure_abs, pressure_msl,\n" + " wind_speed, wind_gust, wind_direction, wind_direction_text, rain)\n" + "VALUES\n" + " (\"%s\", \"%s\", \"%s\", %d, %6.1f, %6.1f, " + " %d, %6.1f, %6.1f, %6.1f, %6.1f, " + " %6.1f, %6.1f, %6.1f, \"%s\", %6.1f);", + config.db_table, + config.station_id, + reading_date, + reading_time, + readings->inside_humidity, + readings->inside_temp, + readings->inside_dewpoint, + readings->outside_humidity, + readings->outside_temp, + readings->outside_dewpoint, + readings->pressure, + readings->pressure_sea_level, + readings->wind_speed, + readings->wind_gust, + readings->wind_direction, + readings->wind_direction_text, + readings->rain - db_previous_rain ); + db_previous_rain = readings->rain; /* update our current rainfall XXX fix this */ + if (update) /* only if updates set */ + { + if (mysql_query (mysql, mysql_querytext)) + { + fprintf (stderr, + "Can't insert database record: %s (%d)\n", + mysql_error (mysql), + mysql_errno (mysql) ); + } + } + else if (verbose) + puts (mysql_querytext); +} + +/* + * Check if we have missed any updates since we last ran. + * + * Not so affectionately named after Powercor (http://www.powercor.com.au/), + * whose continual power outages are the main reason that this function is + * needed. + * + * XXX check this for relationship to share file. + */ +void recover_powercor_breakage () +{ + int page; + time_t reading_time; + + read_page0 (); + if (current_readings.page != page0.current_page) /* we've moved on */ + { + /* Go back to the previous record to read rain */ + if ((page = current_readings.page - WH1080_ARCHIVE_RECORD_SIZE) + < WH1080_FIRST_ARCHIVE) /* wrap around */ + page = WH1080_LAST_ARCHIVE; + reading_time = current_readings.timestamp /* time of this reading */ + - (current_readings.last_save_mins * 60); + read_readings (page, ¤t_readings); + do + { + /* Next page, with wraparound */ + page += WH1080_ARCHIVE_RECORD_SIZE; + if (page > WH1080_LAST_ARCHIVE) + page = WH1080_FIRST_ARCHIVE; + /* + * The archive records appear to be every 30 minutes, but since the + * duration is stored in them, there's no reason not to calculate the + * time the record was completed. + */ + read_readings (page, ¤t_readings); + reading_time += current_readings.last_save_mins * 60; /* time this record was finished */ + current_readings.timestamp = reading_time; + insert_db_row (¤t_readings); + if (verbose) + print_readings (¤t_readings); + } + while (page != page0.current_page); + } +} + +void usage (char *me) +{ + fprintf (stderr, + "Usage: %s [-d] [-n] [-v] [station ID] [db user] [password] [db host] [database]\n", + me); + exit (1); +} + + +int main (int argc, char *argv []) +{ + read_config (argc, argv); + + device_setup (); /* initialize the device */ + if (recover) + recover_powercor_breakage (); /* check for missed updates */ + read_station_data (); /* at least to set the rainfall counter */ + + /* + * Rainfall is a pain. We have several ways of reporting it: + * + * Print out from this program. + * Print out in various forms from report. + * Insert into database. + * Inform Wunderground. + * + * Each of these can be done asynchronously, so we maintain cumulative values of + * current rainfall and a rainfall value for each of these reporting methods. + * They are: + * + * Print out from this program (really util.c) + * current_readings.rain - print_previous_rain + * Inform Wunderground. + * current_readings.rain - current_readings.previous_rain + * Print out from report + * This is done from util.c as well, so we need to set print_previous_rain + * to current_readings.previous_rain + * Insert into database. + * Done from wh1080, + * current_readings.rain - db_previous_rain + */ + current_readings.rain = 0.0; /* And current rainfall */ + current_readings.previous_rain = 0.0; /* Initialize previous rainfall for report */ + print_previous_rain = 0.0; + db_previous_rain = 0.0; + + if (debug) + { + dump_memory (); /* show old data */ + exit (0); + } + + while (1) + { + read_station_data (); + insert_db_row (¤t_readings); + if (verbose) + print_readings (¤t_readings); + + /* XXX calculate the exact time to wait, which will be marginally smaller */ + sleep (config.poll_interval); + previous_readings = current_readings; /* make a copy of the current readings */ + } + /* If we ever get here, this is what we should do */ + usb_release_interface (station, 0); + return 0; +} diff -r 000000000000 -r 9dab44dcb331 wh1080.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wh1080.h Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,187 @@ +/* + * WH-1080 data input utility. + * + * Greg Lehey, 16 November 2009 + * + * $Id: wh1080.h,v 1.9 2010/02/07 03:41:07 grog Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Station description */ +#include "wh1080_dev.h" + +/* Program-dependent stuff. */ +#define WH1080_MAX_RETRIES 5 +#define WH1080_READ_TIMEOUT 1000 +#define WH1080_WRITE_TIMEOUT 1000 + +#define STAMPSIZE 32 /* size of time stamp to print out */ +#define DATETEXTLENGTH 48 /* more than maximum size of date text */ +#define SECSPERHOUR 3600 /* seconds per hour */ +#define SECSPERDAY 86400 /* and seconds per day */ + +/* Conversion factors to archaic units, for Wunderground and friends */ +#define KM_TO_MILES 0.6213711922 +#define MM_TO_IN 0.039370078 +#define C_TO_F(C) (C * 1.8 + 32) +#define hPa_TO_inHg 0.0295299801 +/* + * For some reason, Wunderground doesn't adhere to this constant. By + * observation, the constant must be something like this. + * + * 29.87 / 1011.4 = 0.029533320 + * 29.88 / 1011.7 = 0.029534446 + * 29.89 / 1012.1 = 0.029532654 + * 29.90 / 1012.4 = 0.029533781 + * Average 0.02953553 + * + */ +#define Wunder_hPa_TO_inHg 0.02953553 + + +/* OK, this one's not so archaic */ +#define KELVIN(C) (C + 273.15) + +struct usb_dev_handle { + int fd; + + struct usb_bus *bus; + struct usb_device *device; + + int config; + int interface; + int altsetting; + + /* Added by RMT so implementations can store other per-open-device data */ + int *impl_info; +}; + +/* + * Local configuration, to be read from configuration file. + * If this changes, also change the table definition in db/mkdb. + */ +struct config +{ + int version; /* format version, for DB */ +#define CONFIG_VERSION 1 /* current format version */ + float latitude; /* In ° */ + float longitude; + float elevation; /* metres */ + float pressure_error; /* difference between actual and real pressure, hPa */ + int poll_interval; /* time, in seconds, between reading the device */ + char *station_id; /* this is the one we use for the database */ + char *address; /* Address to display on web pages */ + char *wunderground_station_id; + char *wunderground_passwd; + int wunderground_report_interval; /* how many seconds between reports to Wunderground */ + char *weatheforyou_station_id; + char *weatheforyou_passwd; + int weatherforyou_report_interval; /* how many seconds between reports to Weather for you */ + int website_generation_interval; /* how many seconds between generating PHP header file */ + char *db_host; /* database on this host */ + char *db_user; /* user ID on host */ + char *db_passwd; /* and password */ + char *db; /* database name */ + char *db_table; /* table */ + char *php_header; /* php header file with weather data */ + char *comparefile; /* file to compare local weather stations */ +}; + +extern struct config config; +/* Command line options, defined in db.c */ +extern int debug; /* -d */ +extern int update; /* -n turns updating off */ +extern int recover; /* -r: recover missed archive data (wh1080 only) */ +extern int verbose; /* -v */ +extern int once; /* -1 */ + +/* MySQL stuff */ +extern MYSQL *mysql; +extern MYSQL_RES *mysql_result; +extern MYSQL_ROW mysql_row; +extern char mysql_querytext [1024]; /* build up queries here */ + + +/* + * Readings in sanitized form. These are effectively the readings from the + * station in a format that's easier for us to use. + */ + +struct readings +{ + int page; /* page from whence the readings came */ + time_t timestamp; /* time of reading */ + int last_save_mins; /* last save minutes (?) */ + float inside_temp; /* inside temperature */ + int inside_humidity; /* humidity in percent */ + float inside_dewpoint; /* dewpoint temperature inside */ + float outside_temp; /* outside temperature */ + int outside_humidity; /* humidity in percent */ + float outside_dewpoint; /* dewpoint temperature outside */ + float pressure; /* absolute pressure, hPa */ + float pressure_sea_level; /* relative sea level pressure, hPa */ + float page1_abs_pressure; /* absolute pressure, hPa, from page 1 */ + float page1_rel_pressure; /* relative pressure, hPa, from page 1 */ + float wind_speed; /* wind speed in km/h */ + float wind_gust; /* wind gust speed in km/h */ + float wind_direction; /* wind direction, as angle */ +#define INVALID_DIRECTION 1024 /* set if we get invalid readings */ + char wind_direction_text [4]; /* wind direction, in text */ + /* + * We keep two rain counters. rain is the current rainfall, incremented on + * every reading. This is similar to, but not the same as, what the station + * does internally. This field is only changed by the wh1080 program. + * + * The other field, 'previous_rain', is set to 'rain' by the report program + * after reporting the rainfall. It is only changed by report. This approach + * avoids any kind of locking. + */ + float rain; /* rainfall in mm */ + float previous_rain; /* previous rainfall in mm */ +}; + +extern struct readings current_readings; + +struct sharefile +{ +#define SHARED_MAGIC 0x539 + int magic; /* magic number */ + struct readings current_readings; /* current readings */ +}; + +extern struct sharefile *sharefile; /* file to map */ +extern int sharefd; /* and descriptor */ +extern float print_previous_rain; + +/* MySQL stuff, in db.c */ +extern MYSQL *mysql; +extern MYSQL_RES *mysql_result; +extern MYSQL_ROW mysql_row; +extern char mysql_querytext [1024]; /* build up queries here */ +extern char *wind_directions []; /* in util.c */ + +/* in db.c */ +void db_login (); +int doquery (char *query); +int domyquery (char *query, MYSQL_RES **result); +char *mycopy (char *); +int read_config (int argc, char *argv []); + +void print_readings (struct readings *readings); +float dewpoint (float temp, int humidity); +void set_dewpoints (struct readings *readings); +void set_sea_level_pressure (struct readings *readings); +void datetext (time_t, char *, char *); diff -r 000000000000 -r 9dab44dcb331 wh1080_dev.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wh1080_dev.h Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,123 @@ +/* + * WH-1080 data input utility. + * This bases on code supplied to me by Steve Woodford. + * + * Greg Lehey, 16 November 2009 + * + * $Id: wh1080_dev.h,v 1.4 2009/12/08 01:03:12 grog Exp grog $ + */ + +#ifdef linux +/* + * I wish I understood linux header files. This whole thing looks like a mess. + * If you can explain it to me, please do. + */ +#include +#include +#define htobe16 __cpu_to_be16 +#define le16toh __le16_to_cpu +#include +#else +#include +#endif +/* + * This file describes the WH-1080 weather station as well as I can understand + * it. The station is not documented, and I've pulled this together from + * various source code written by others without access to documentation. + */ + +/* Vendor and product number for WH-1080 */ +#define WH1080_USB_VENDOR 0x1941 +#define WH1080_USB_PRODUCT 0x8021 + +#define WH1080_PAGE_SIZE 32 +#define WH1080_PAGE_MASK (~ (WH1080_PAGE_SIZE - 1)) + +/* Offsets of the first 2 pages */ +#define WH1080_PAGE0 0 +#define WH1080_PAGE1 0x20 +/* First archive record appears to start here */ +#define WH1080_FIRST_ARCHIVE 0x100 +/* And the last one must be here */ +#define WH1080_LAST_ARCHIVE 0xfff0 +/* And thus there appear to be this many of them. */ +#define WH1080_ARCHIVE_COUNT 4088 +/* The records swap this often, in seconds */ +#define WH1080_ARCHIVE_INTERVAL 1800 +/* Size of archive record */ +#define WH1080_ARCHIVE_RECORD_SIZE 16 + +/* Descriptor stuff. I'm not sure what this is all about. XXX */ +#define WH1080_DESCRIPTOR1_SIZE 18 +#define WH1080_DESCRIPTOR2_SIZE 9 +/* + * Steve Woodford has: + * #define WH1080_DESCRIPTOR3_SIZE 34 + * #define WH1080_DESCRIPTOR34_SIZE 0x74 + */ +#define WH1080_DESCRIPTOR3_SIZE 34 +#define WH1080_DESCRIPTOR34_SIZE 0x74 + +/* + * The device appears to have its data stored in pages of 32 bytes. I have very + * little information beyond what I've gleaned from other people's code, so some + * of this looks very strange. + * + * The following structures define what we know of the first two pages. + */ +struct wh1080_page0 +{ + uint64_t magic; /* one of apparently 2 different magic numbers */ + uint64_t magic2; + char mumble [14]; + uint16_t current_page; /* byte offset of current readings */ +}; + +/* These seem to be alternate values. No idea why. */ +#if BYTE_ORDER == LITTLE_ENDIAN +/* XXX find out which endian this is */ +#define WH1080_PAGE0_MAGIC1A 0xffffffffffffaa55ull +#define WH1080_PAGE0_MAGIC1B 0xffffffffffaaaa55ull +#define WH1080_PAGE0_MAGIC2 0xffffffffffffffffull +#else +#define WH1080_PAGE0_MAGIC1A 0x55aaffffffffffffull +#define WH1080_PAGE0_MAGIC1B 0x55aaaaffffffffffull +#define WH1080_PAGE0_MAGIC2 0xffffffffffffffffull +#endif +/* It's not clear what the purpose of page 1 is. One pressure reading comes with the current data. */ +struct wh1080_page1 +{ + uint16_t rel_pressure; /* hPa / 10 */ + uint16_t abs_pressure; /* hPa / 10 */ + char mumble1 [28]; +}; + +/* + * At least current weather data looks like this. This record should be exactly + * 16 bytes long. + */ +struct wh1080_readings +{ /* offset: */ + uint8_t last_save_mins; /* 0: last save minutes (?) */ + uint8_t inside_humidity; /* 1: humidity in percent */ + int16_t inside_temp; /* 2: inside temperature, °C/10 */ + uint8_t outside_humidity; /* 4: humidity in percent */ + int16_t outside_temp; /* 5: outside temperature, °C/10 */ + uint16_t pressure; /* 7: absolute pressure, hPa / 10 */ + uint8_t wind_speed; /* 9: wind speed in m/s/10, up to 25.5 m/s */ + uint8_t wind_gust; /* 10: wind gust speed in m/s/10, up to 25.5 m/s */ + char mumble1; /* 11: apparently unused byte */ + /* + * wind direction is a number between 0 and 15, repesenting increments of + * 22.5°. It may also have the value 0x80 if there's no wind at all. + */ + uint8_t wind_direction; /* 12: */ + /* + * Rainfall is measured in units of 0.3 mm, and the value returned is a + * counter. There's only real rainfall if the value returned here is + * different from the previous reading. + */ + uint16_t rain; /* 13: */ + char mumble2; /* 15: pad to 16 bytes */ +} __packed; + diff -r 000000000000 -r 9dab44dcb331 wundersend --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wundersend Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,10 @@ +#!/bin/sh +# $Id: wundersend,v 1.1 2009/11/22 23:33:06 grog Exp $ +# Send a report to Wunderground and log if it fails +RESULTFILE=/var/tmp/wunderground.$$ +fetch -o $RESULTFILE $1 +RESULT=`cat $RESULTFILE` +if [ "$RESULT" != "success" ]; then + logger "wh1080: Can't send update to Wunderground: $RESULT" +fi +rm $RESULTFILE diff -r 000000000000 -r 9dab44dcb331 xreport.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xreport.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,371 @@ +/* + * Report weather conditions to remote web sites. + * + * Greg Lehey, 14 December 2009 + * + * $Id: xreport.c,v 1.3 2010/02/07 03:41:35 grog Exp $ + */ + +#include "wh1080.h" + +#define STAMPSIZE 32 /* size of time stamp to print out */ + +struct readings current_readings; /* current data readings, our version */ +char reporturl [2048]; /* build up report here */ +time_t last_report; /* time of last report */ + +/* + * Data returned by database query, in the form we want. + */ +struct weather_query +{ + float inside_humidity; + float inside_temp; + float inside_dewpoint; + float outside_humidity; + float outside_temp; + float outside_dewpoint; + float pressure_msl; + float wind_speed; + float wind_gust; + float wind_direction; + float rain; +} weather_query; + +/* + * Update the last report time. + */ +void update_lastreport (time_t time) +{ + char report_time [STAMPSIZE]; + + strftime (report_time, /* format time and date */ + STAMPSIZE, + "%Y-%m-%d %T", + localtime (&time)); + sprintf (mysql_querytext, + "REPLACE INTO lastreport (station_id, report_id, date)\n" + "VALUES (\"%s\", \"Wunderground\", \"%s\")", + config.wunderground_station_id, + report_time ); + doquery (mysql_querytext); +} + +/* + * Send information to Wunderground. + * + * See http://wiki.wunderground.com/index.php/PWS_-_Upload_Protocol for details + * of communication protocol. + * + * Wunderground requires all values in archaic units, but pressures converted to + * in Hg are converted back about 0.2 hPa too low. We use a different + * definition, which may or may not get the in Hg right, but results in correct + * conversion back to hPa. + * + * Return 0 on success, 1 on failure. Failure will be retried at the next + * reporting interval. + */ + +int really_inform_wunderground (time_t start, time_t end) +{ + /* Care: we can't add start and end, because they'll overflow */ + time_t midtime = start / 2 + end / 2; /* average time of the interval */ + char dateutc [STAMPSIZE]; + int something = 0; /* set when we find something to report */ + int i; /* counter */ + + /* Values to report */ + float inside_humidity; + float inside_temp; +#if 0 + float inside_dewpoint; /* not currently used */ +#endif + float outside_humidity; + float outside_temp; + float outside_dewpoint; + float pressure_msl; + float wind_speed; + float wind_gust; + float wind_direction; + float rain; + + sprintf (mysql_querytext, + "SELECT AVG(inside_humidity),\n" + " AVG(inside_temp),\n" + " AVG(inside_dewpoint),\n" + " AVG(outside_humidity),\n" + " AVG(outside_temp),\n" + " AVG(outside_dewpoint),\n" + " AVG(pressure_msl),\n" + " AVG(wind_speed),\n" + " AVG(wind_gust),\n" + " AVG(wind_direction)\n" + "FROM observations\n" + "WHERE unix_timestamp(timestamp(date,time)) >= %d\n" + " AND unix_timestamp(timestamp(date, time)) <= %d\n" + " AND station_id = \"%s\"\n", + start, + end, + config.station_id ); + + if (doquery (mysql_querytext)) + return 1; /* failed and reported */ + + if (verbose) + { + strftime (dateutc, /* format time and date */ + STAMPSIZE, + "%Y-%m-%d %T", + localtime (&start)); + printf ("Start date:\t\t%17s\n", dateutc); + strftime (dateutc, /* format time and date */ + STAMPSIZE, + "%Y-%m-%d %T", + localtime (&end)); + printf ("End date:\t\t%17s\n", dateutc); + } + + if (mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + { +#if 0 + /* + * What a mess! The result of the query in in text form, and we need to convert it to float + * both to reformat it and to convert units. + */ + printf ("%s %s %s %s %s %s %s %s %s %s %s\n", + mysql_row [0], + mysql_row [1], + mysql_row [2], + mysql_row [3], + mysql_row [4], + mysql_row [5], + mysql_row [6], + mysql_row [7], + mysql_row [8], + mysql_row [9], + mysql_row [10] ); +#endif + /* + * Any of these values may be NULL. If they all are, we don't have any data, so + * wait for some to show up. + */ + for (i = 0; i < 11; i++) + if (mysql_row [i]) + something = 1; + if (! something) /* nothing to report */ + return 1; /* consider a "failure" */ + + strftime (dateutc, STAMPSIZE, "%F+%H%%3a%M%%3a%S", gmtime (&midtime)); /* format time and date */ + + /* Start building up the message */ + sprintf (reporturl, + "./wundersend " + "'http://weatherstation.wunderground.com/weatherstation/updateweatherstation.php" + "?ID=%s&PASSWORD=%s" + "&dateutc=%s", + config.wunderground_station_id, + config.wunderground_passwd, + dateutc ); + + /* Now find what we have to report */ + if (mysql_row [0]) + { + inside_humidity = atof (mysql_row [0]); + if (verbose) + printf ("Inside humidity:\t%4.0f%%\n", inside_humidity); + sprintf (&reporturl [strlen (reporturl)], + "&indoorhumidity=%1.0f", + inside_humidity ); + } + if (mysql_row [1]) + { + inside_temp = atof (mysql_row [1]); + if (verbose) + printf ("Inside temperature:\t%6.1f°\n", inside_temp); + inside_temp = C_TO_F (inside_temp); + sprintf (&reporturl [strlen (reporturl)], + "&indoortempf=%2.1f", + inside_temp ); + } + /* mysql_row [2] is inside dewpoint, which we don't seem to be able to report */ + if (mysql_row [3]) + { + outside_humidity = atof (mysql_row [3]); + if (verbose) + printf ("Outside humidity:\t%4.0f%%\n", outside_humidity); + sprintf (&reporturl [strlen (reporturl)], + "&humidity=%1.0f", + outside_humidity ); + } + if (mysql_row [4]) + { + outside_temp = atof (mysql_row [4]); + if (verbose) + printf ("Outside temperature:\t%6.1f°\n", outside_temp); + outside_temp = C_TO_F (atof (mysql_row [4])); + sprintf (&reporturl [strlen (reporturl)], + "&tempf=%2.1f", + outside_temp ); + } + if (mysql_row [5]) + { + outside_dewpoint = atof (mysql_row [5]); + if (verbose) + printf ("Outside dewpoint:\t%6.1f°\n", outside_dewpoint); + outside_dewpoint = C_TO_F (outside_dewpoint); + sprintf (&reporturl [strlen (reporturl)], + "&dewptf=%2.1f", + outside_dewpoint ); + } + if (mysql_row [6]) + { + pressure_msl = atof (mysql_row [6]); + if (verbose) + printf ("Sea level pressure:\t%6.1f hPa\n", pressure_msl); + pressure_msl *= Wunder_hPa_TO_inHg; + sprintf (&reporturl [strlen (reporturl)], + "&baromin=%4.4f", + pressure_msl ); + } + if (mysql_row [7]) + { + wind_speed = atof (mysql_row [7]); + if (verbose) + printf ("Wind speed:\t\t%6.1f km/h\n", wind_speed); + wind_speed *= KM_TO_MILES; + sprintf (&reporturl [strlen (reporturl)], + "&windspeedmph=%2.1f", + wind_speed ); + } + if (mysql_row [8]) + { + wind_gust = atof (mysql_row [8]); + if (verbose) + printf ("Wind gust:\t\t%6.1f km/h\n", wind_gust); + wind_gust *= KM_TO_MILES; + sprintf (&reporturl [strlen (reporturl)], + "&windgustmph=%2.1f", + wind_gust ); + } + if (mysql_row [9]) /* wind direction */ + { + wind_direction = atof (mysql_row [9]); + if (verbose) + printf ("Wind dir:\t\t%4.0f°\n", wind_direction); + sprintf (&reporturl [strlen (reporturl)], + "&winddir=%1.0f", + wind_direction ); + } + mysql_free_result (mysql_result); + + /* + * Now do the rain. This is special because it has to be per hour. + */ + start = end - SECSPERHOUR; + sprintf (mysql_querytext, + "SELECT SUM(rain)\n" + "FROM observations\n" + "WHERE unix_timestamp(timestamp(date,time)) >= %d\n" + " AND unix_timestamp(timestamp(date, time)) <= %d\n" + " AND station_id = \"%s\"\n", + start, + end, + config.station_id ); + + if (doquery (mysql_querytext) == 0) /* succeeded */ + { + if ((mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + && mysql_row [0] ) /* and it's not NULL, */ + { + rain = atof (mysql_row [0]); + if (verbose) + printf ("Rain per hour:\t\t%6.1f mm\n", rain); + rain *= MM_TO_IN; + sprintf (&reporturl [strlen (reporturl)], + "&rainin=%2.1f", + rain ); + } + } + + sprintf (&reporturl [strlen (reporturl)], + "&softwaretype=dereel-weather&action=updateraw'" ); + if (verbose) + printf ("\n"); /* end out output line */ + if (debug) + puts (reporturl); + if (update) + system (reporturl); + } + return 0; +} + +void inform_wunderground () +{ + time_t now; /* and time now */ + + sprintf (mysql_querytext, + "SELECT unix_timestamp(date) FROM lastreport\n" + " WHERE station_id =\"%s\"\n" + " AND report_id = \"Wunderground\"", + config.wunderground_station_id ); + + if (doquery (mysql_querytext)) /* silently ignore errors */ + return; + now = time (NULL); + if (mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + last_report = atoi (mysql_row [0]); + else + last_report = now - config.wunderground_report_interval; + mysql_free_result (mysql_result); + + /* + * Report in strict intervals. + */ + while ((now - last_report) >= config.wunderground_report_interval) + { + if (really_inform_wunderground (last_report, last_report + config.wunderground_report_interval)) + return; /* failure, we'll retry later */ + last_report += config.wunderground_report_interval; + update_lastreport (last_report); + now = time (NULL); /* time moves on */ + } +} + +void usage (char *me) +{ + fprintf (stderr, + "Usage: %s [-d] [-n] [-v] [station ID] [db user] [password] [db host] [database]\n", + me); + exit (1); +} + +int main (int argc, char *argv []) +{ + time_t nextrun; + time_t now; + + if (read_config (argc, argv) < 0) + usage (argv [0]); + + if (! config.wunderground_station_id) + { + fprintf (stderr, "No wunderground station ID in config\n"); + exit (1); + } + + while (1) + { + inform_wunderground (); + if (once) /* just run once */ + exit (0); + /* + * Informing Wunderground takes finite time. Stay on schedule. + * We may end up off by a second from time to time, but we shouldn't drift. + * + */ + nextrun = last_report + config.wunderground_report_interval; /* next time to run */ + now = time (NULL); + sleep (nextrun - now); + } + return 0; +} diff -r 000000000000 -r 9dab44dcb331 yreport.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/yreport.c Tue Feb 09 13:44:25 2010 +1030 @@ -0,0 +1,301 @@ +/* + * Report weather conditions to remote web sites. + * + * Greg Lehey, 14 December 2009 + * + * $Id: yreport.c,v 1.2 2010/02/07 03:39:12 grog Exp $ + */ + +#include "wh1080.h" + +#define STAMPSIZE 32 /* size of time stamp to print out */ + +/* + * Data returned by database query, in the form we want. + */ +struct weather_query +{ + float inside_humidity; + float inside_temp; + float inside_dewpoint; + float outside_humidity; + float outside_temp; + float outside_dewpoint; + float pressure_msl; + float wind_speed; + float wind_gust; + float wind_direction; + float rain; +} weather_query; + +/* Format strings for printing out variables */ +char *rowformat1 [] = {"\t$inside_humidity = %4.0f;\n", + "\t$inside_temperature = %2.1f;\n", + "\t$inside_dewpoint = %2.1f;\n", + "\t$outside_humidity = %4.0f;\n", + "\t$outside_temperature = %2.1f;\n", + "\t$outside_dewpoint = %2.1f;\n", + "\t$pressure_msl = %2.1f;\n", + "\t$wind_speed = %2.1f;\n", + "\t$wind_gust = %2.1f;\n", + "\t$wind_direction = %4.0f;\n", + "\t$rain = %2.1f;\n"}; + +char *rowformat2 [] = {"\t$max_inside_humidity = %2.3f;\n", + "\t$min_inside_humidity = %2.3f;\n", + "\t$max_inside_temp = %2.3f;\n", + "\t$min_inside_temp = %2.3f;\n", + "\t$max_inside_dewpoint = %2.3f;\n", + "\t$min_inside_dewpoint = %2.3f;\n", + "\t$max_outside_humidity = %2.3f;\n", + "\t$min_outside_humidity = %2.3f;\n", + "\t$max_outside_temp = %2.3f;\n", + "\t$min_outside_temp = %2.3f;\n", + "\t$max_outside_dewpoint = %2.3f;\n", + "\t$min_outside_dewpoint = %2.3f;\n", + "\t$max_pressure_msl = %2.3f;\n", + "\t$min_pressure_msl = %2.3f;\n", + "\t$max_wind_speed = %2.3f;\n", + "\t$min_wind_speed = %2.3f;\n", + "\t$max_wind_gust = %2.3f;\n", + "\t$min_wind_gust = %2.3f;\n", + "\t$sum_rain = %2.3f;\n" }; + +char *maxmintimes [] = {"inside_humidity", + "inside_temp", + "inside_dewpoint", + "outside_humidity", + "outside_temp", + "outside_dewpoint", + "pressure_msl", + "wind_speed", + "wind_gust" }; + +int rowformats1 = sizeof (rowformat1) / sizeof (char *); +int rowformats2 = sizeof (rowformat2) / sizeof (char *); +int maxmincount = sizeof (maxmintimes) / sizeof (char *); + +/* + * Create a header file for PHP pages. The "current readings" are an average + * over the last config.website_generation_interval seconds. + */ + +/* + * An alternative query method could be prepared statements. See + * http://dev.mysql.com/doc/refman/5.1/en/c-api-prepared-statements.html for + * details. Peter Jeremy says: "Search for MYSQL_BIND, mysql_stmt_bind_param() + * and mysql_stmt_bind_result()." + */ +void make_php_header_file () +{ + time_t start; + time_t now; + FILE *header_file; + char date_time [DATETEXTLENGTH]; /* Wednesday, 25 November 2009 16:47:46 */ + time_t yesterday; + char yesterday_text [DATETEXTLENGTH]; /* YYYY-MM-DD */ + int row; + int rough_direction; /* out of range */ + + header_file = fopen (config.php_header, "w"); + if (! header_file) + fprintf (stderr, + "Can't open %s: %s (%d)\n", + config.php_header, + strerror (errno), + errno ); + else + { + now = time (NULL); + start = now - config.website_generation_interval; /* determine the time we're doing this for */ + strftime (date_time, DATETEXTLENGTH, "%A, %e %B %Y %T", localtime (&now)); + yesterday = now - SECSPERDAY; /* this time yesterday */ + strftime (yesterday_text, DATETEXTLENGTH, "%F", localtime (&yesterday)); + + fprintf (header_file, + "= %d\n" + " AND unix_timestamp(timestamp(date, time)) <= %d\n" + " AND station_id = \"%s\"\n", + config.db_table, + start, + now, + config.station_id ); + + if (doquery (mysql_querytext)) + return; /* failed and reported */ + + if (mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + { + for (row = 0; row < rowformats1; row++) + { + /* + * We have a problem here: if a value is missing, we can't just print + * it. Fake a number for the time being. + */ + if (mysql_row [row] && *mysql_row [row]) /* something there */ + fprintf (header_file, rowformat1 [row], atof (mysql_row [row])); + else + fprintf (header_file, rowformat1 [row], -999.0); + } + + /* Isn't this ugly? */ + rough_direction = 1000; /* out of range */ + if ((mysql_row [9]) && (*mysql_row [9])) /* something there */ + rough_direction = (int) ((atof (mysql_row [9]) + 11.25) / 22.5); + + if ((rough_direction < 16) && (rough_direction >= 0)) + fprintf (header_file, + "\t$wind_direction_text = \"%s\";\n", + wind_directions [rough_direction] ) ; + else + fprintf (header_file, "\t$wind_direction_text = \"(none)\";\n"); + mysql_free_result (mysql_result); + } + + /* Now stats for the day */ + strftime (date_time, DATETEXTLENGTH, "%Y-%m-%d", localtime (&now)); + sprintf (mysql_querytext, + "SELECT @max_inside_humidity := MAX(inside_humidity),\n" + " @min_inside_humidity := MIN(inside_humidity),\n" + " @max_inside_temp := MAX(inside_temp),\n" + " @min_inside_temp := MIN(inside_temp),\n" + " @max_inside_dewpoint := MAX(inside_dewpoint),\n" + " @min_inside_dewpoint := MIN(inside_dewpoint),\n" + " @max_outside_humidity := MAX(outside_humidity),\n" + " @min_outside_humidity := MIN(outside_humidity),\n" + " @max_outside_temp := MAX(outside_temp),\n" + " @min_outside_temp := MIN(outside_temp),\n" + " @max_outside_dewpoint := MAX(outside_dewpoint),\n" + " @min_outside_dewpoint := MIN(outside_dewpoint),\n" + " @max_pressure_msl := MAX(pressure_msl),\n" + " @min_pressure_msl := MIN(pressure_msl),\n" + " @max_wind_speed := MAX(wind_speed),\n" + " @min_wind_speed := MIN(wind_speed),\n" + " @max_wind_gust := MAX(wind_gust),\n" + " @min_wind_gust := MIN(wind_gust),\n" + " SUM(rain)\n" + "FROM %s\n" + "WHERE date = \"%s\"\n" + " AND station_id = \"%s\"\n", + config.db_table, + date_time, + config.station_id ); + + if (doquery (mysql_querytext)) + return; /* failed and reported */ + + if (mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + { + for (row = 0; row < rowformats2; row++) + fprintf (header_file, rowformat2 [row], atof (mysql_row [row])); + mysql_free_result (mysql_result); + } + + /* Now get the times that go with the maxima and minima */ + for (row = 0; row < maxmincount; row++) + { + /* Time of maximum */ + sprintf (mysql_querytext, + "SELECT time from %s\n" + "WHERE date = \"%s\"\n" + " AND station_id = \"%s\"\n" + " AND %s = @max_%s\n" + "LIMIT 1", + config.db_table, + date_time, + config.station_id, + maxmintimes [row], + maxmintimes [row] ); + if (doquery (mysql_querytext)) /* failure */ + return; /* XXX be cleverer */ + if (mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + { + fprintf (header_file, "\t$max_%s_time = \"%s\";\n", maxmintimes [row], mysql_row [0]); + mysql_free_result (mysql_result); + } + else /* dummy */ + fprintf (header_file, "\t$max_%s_time = \"unknown\"\n", maxmintimes [row]); + /* Now time of minimum */ + sprintf (mysql_querytext, + "SELECT time from %s\n" + "WHERE date = \"%s\"\n" + " AND station_id = \"%s\"\n" + " AND %s = @min_%s\n" + "LIMIT 1", + config.db_table, + date_time, + config.station_id, + maxmintimes [row], + maxmintimes [row] ); + if (doquery (mysql_querytext)) /* failure */ + return; /* XXX be cleverer */ + if (mysql_row = mysql_fetch_row (mysql_result)) /* got something */ + { + fprintf (header_file, "\t$min_%s_time = \"%s\";\n", maxmintimes [row], mysql_row [0]); + mysql_free_result (mysql_result); + } + else /* dummy */ + fprintf (header_file, "\t$min_%s_time = \"unknown\"\n", maxmintimes [row]); + } + + fprintf (header_file, "\t?>\n"); /* end */ + fclose (header_file); + } +} + +void usage (char *me) +{ + fprintf (stderr, + "Usage: %s [-d] [-n] [-v] [-1] [station ID] [db user] [password] [db host] [database]\n", + me); + exit (1); +} + +int main (int argc, char *argv []) +{ + time_t lastrun; + time_t nextrun; + time_t now; + + if (read_config (argc, argv) < 0) + usage (argv [0]); + + while (1) + { + lastrun = time (NULL); /* time we ran the loop */ + make_php_header_file (); + if (once) /* just run once */ + exit (0); + /* + * Informing Wunderground takes finite time. Stay on schedule. + * We may end up off by a second from time to time, but we shouldn't drift. + * + */ + nextrun = lastrun + config.website_generation_interval; /* next time to run */ + now = time (NULL); + sleep (nextrun - now); + } + return 0; +}