changeset 0:9dab44dcb331

Initial commit of Greg's code from http://www.lemis.com/grog/tmp/wh1080.tar.gz
author Daniel O'Connor <darius@dons.net.au>
date Tue, 09 Feb 2010 13:44:25 +1030
parents
children 01496de9f722
files LICENSE Makefile README db.c db/.gdbinit db/RCS db/README db/convertme db/getremote-today db/getwunderday db/insertBoM db/insertBoM.c db/insertwunderground db/insertwunderground-0 db/insertwunderground.awk db/insertwunderground.c db/insertwunderground.sed db/insertwunderground.sh db/junk/getremote db/junk/getwunder db/mkconfig db/mkdb db/tidy-observations local-compare.c plots/.1 plots/.2 plots/OUTFILE plots/RCS plots/doplots plots/myplot plots/plot-BoM-Dereel-pressure.gnuplot plots/plot-BoM-Dereel.gnuplot plots/plot-common.gnuplot plots/plot-common2.gnuplot plots/plot-common3.gnuplot plots/plot-comparative-dewpoint.gnuplot plots/plot-comparative-gust.gnuplot plots/plot-comparative-humidity.gnuplot plots/plot-comparative-pressure.gnuplot plots/plot-comparative-rain.gnuplot plots/plot-comparative-temperature.gnuplot plots/plot-comparative-wind.gnuplot plots/plot-humidity.gnuplot plots/plot-inside-outside-temp.gnuplot plots/plot-inside-temp.gnuplot plots/plot-outside-temp.gnuplot plots/plot-pressure.gnuplot plots/plot-rain.gnuplot plots/plot-temperatures.gnuplot plots/plot-wind.gnuplot plots/plot5 util.c web/RCS web/comparison.php web/db.php web/db2.php web/index.php wh1080.c wh1080.h wh1080_dev.h wundersend xreport.c yreport.c
diffstat 60 files changed, 5075 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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.
--- /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 $@ $<
--- /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.
+
--- /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 */
+}
+
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
Binary file db/insertBoM has changed
--- /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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#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;
+}
+
Binary file db/insertwunderground has changed
--- /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
--- /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 );
+}
+
+
--- /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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+
+#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;
+}
+
--- /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
--- /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
--- /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="
--- /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
--- /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",
+);
--- /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)  	      
+);
--- /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;
--- /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 <syslog.h>
+
+#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);
+}
--- /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
--- /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
--- /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
--- /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 
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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, \
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
--- /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
+
--- /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
--- /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
--- /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
--- /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);
+}
--- /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
--- /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 @@
+<!-- for Emacs, this is a -*- mode: html-fill; coding: utf-8 -*- document -->
+<!-- $Id: comparison.php,v 1.8 2010/02/08 01:46:47 grog Exp $ -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<?php
+   /* title should start with a lower case character */
+   $title = "Comparative weather observations";
+   $subtitle = "Ballarat area";
+   include "header.php";
+   include "weathergraph.php";
+   include "current.php";
+   $id = '$Id: comparison.php,v 1.8 2010/02/08 01:46:47 grog Exp $';
+   if (array_key_exists ("date", $_GET))
+    $date = $_GET ["date"];
+   else
+    $date = date ("Y-m-d");
+
+?>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <?php pagetitle0 ($title); ?>
+    <meta http-equiv="refresh" content="900" ;="">
+  </head>
+
+  <body>
+    <?php pageheader0 ($title); ?>
+
+    <div align="justify">
+      <p>
+	This page shows comparative readings from a number of weather stations.  It's still
+	experimental.  This compares data from the following weather stations in <?php href
+	("http://www.wunderground.com/wundermap/?lat=-37.76312&lon=144.01566&zoom=10&type=map&units=metric&rad=1&rad.num=1&rad.spd=25&rad.opa=70&rad.stm=0&rad.type=N0R&rad.smo=1&rad.mrg=0&wxsn=1&wxsn.mode=tw&svr=0&cams=0&sat=0&riv=0&mm=0&hur=0&fire=0&tor=0&ndfd=0&pix=0",
+	"the area"); ?>, and also Melbourne airport, a little further away (<?php href
+	("http://www.wunderground.com/history/station/94868/2009/12/22/DailyHistory.html",
+	"Melbourne city"); ?> doesn't report wind).
+      </p>
+
+      <ul>
+	<li>
+	  <?php href
+          ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI102", "Bacchus
+          Marsh airport"); ?> (currently offline).
+	</li>
+	<li>
+	  <?php href ("http://www.bom.gov.au/products/IDV60801/IDV60801.94852.shtml", "Ballarat");
+	  ?> airport (<?php href ("http://www.bom.gov.au/", "Bureau of Meteorology"); ?>)
+	</li>
+	<li>
+	  <?php href
+	  ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI115",
+	  "Buninyong"); ?>
+	</li>
+	<li>
+	  <?php href ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI94",
+	  "Delacombe"); ?>
+	</li>
+	<li>
+	  <?php href
+	  ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI124",
+	  "Dereel"); ?> (this station)
+	</li>
+
+	<li>
+	  <?php href ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICREDA2",
+	  "Redan"); ?>
+	</li>
+
+	<li>
+	  <?php href ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI86",
+	  "Teesdale"); ?>
+	</li>
+
+	<li>
+	  <?php href ("http://www.bom.gov.au/products/IDV60901/IDV60901.94866.shtml", "Melbourne
+	  Airport"); ?> (<?php href ("http://www.bom.gov.au/", "Bureau of Meteorology"); ?>)
+	</li>
+
+	<li>
+	  <?php href ("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICMTHE3",
+	  "Mount Helen"); ?>
+	</li>
+	<li>
+	  <?php href ("http://www.bom.gov.au/products/IDV60901/IDV60901.94863.shtml", "Sheaoaks");
+	  ?> (<?php href ("http://www.bom.gov.au/", "Bureau of Meteorology"); ?>)
+	</li>
+      </ul>
+
+      <?php
+  if (array_key_exists ("raw", $_GET))
+    {
+      $raw = "-raw";
+	 /* offer to smooth */
+      print <<< EOS
+    <form action="comparison.php" method="get">
+      <input type="submit" name="smoothed" value="Smooth" />
+    </form>
+
+EOS;
+    }
+    else                                        /* set edit */
+    {
+      $raw = "";
+      print <<< EOS
+    <form action="comparison.php" method="get">
+      <input type="submit" name="raw" value="Raw" />
+    </form>
+
+EOS;
+    }
+?>
+    <br />
+
+      <div align="left">
+
+	<?php showgraphs (<<< EOS
+comparative-temperature-$date     Comparative temperature
+comparative-dewpoint-$date        Comparative dewpoint
+comparative-humidity-$date        Comparative humidity
+comparative-pressure-$date        Comparative pressure
+comparative-wind-$date        Comparative wind speed
+comparative-gust-$date        Comparative wind gust speed
+comparative-rain-$date        Comparative rainfall
+EOS
+);
+?>
+      </div>
+    </div>
+
+<?php pagefooter ($id); ?>
--- /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 @@
+<!-- for Emacs, this is a -*- mode: html-fill; coding: utf-8 -*- document -->
+<!-- $Id: db.php,v 1.6 2010/02/07 03:25:04 grog Exp $ -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<?php
+{
+  $title = "Dereel weather observations";
+  $subtitle = "";
+  include "header.php";
+  include "weathergraph.php";
+  $id = '$Id: db.php,v 1.6 2010/02/07 03:25:04 grog Exp $';
+}
+?>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <?php pagetitle0 ($title); ?>
+    <meta http-equiv="refresh" content="900" ;="">
+  </head>
+
+  <body>
+    <?php pageheader0 ($title); ?>
+
+    <div align="justify">
+      <p>
+	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 <?php href
+	("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI124",
+	"Wunderground page"); ?> for this station.
+      </p>
+
+<?php
+
+{
+  $doplot = "/home/grog/src/weather/WH-1080/plots/doplots";
+  $weatherdir = "/home/grog/public_html/weather";
+  $me = basename ($_SERVER ["SCRIPT_FILENAME"]);
+
+  if (array_key_exists ("date", $_GET))
+  {
+    $mydate = validdate ($_GET ["date"], "date");
+    if (is_array ($mydate))                       /* valid date */
+      $date = formatdate ("Y-m-d", $mydate);
+    else
+    {
+      print <<< EOS
+        <p>
+        <font color="red">Invalid date: $mydate.  Using today's date</font>
+        </p>
+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
+
+     <!-- Select new date -->
+    <table>
+      <tr>
+	<td align="left">
+          <form action="$me" method="get">
+	    <input type="submit" value="Previous day"/>
+  	    <input size="20" maxlength="20" type="hidden" name="date" value="$yesterday"/>
+          </form>
+	</td>
+
+	<td>
+	  <form action="$me" method="get">
+	    <table summary="Parameter input" cellspacing="2" border="0">
+              <tr>
+		<td>
+		  <input type="submit" value="New date:"/>
+		</td>
+
+                <!-- value -->
+		<td>
+  		  <input size="20" maxlength="20" type="text" name="date" value="$date" />
+		</td>
+              </tr>
+	    </table>
+	  </form>
+	</td>
+EOS;
+
+    if (! $istoday)
+      print <<< EOS
+	<td align="right">
+           <form action="$me" method="get">
+	         <input type="submit" value="Next day"/>
+  	         <input size="20" maxlength="20" type="hidden" name="date" value="$tomorrow"/>
+           </form>
+	</td>
+
+	<td align="right">
+           <form action="$me" method="get">
+	         <input type="submit" value="Today"/>
+           </form>
+	</td>
+
+EOS;
+  print <<< EOS
+
+      </tr>
+    </table>
+
+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
+        <p>
+        No data found for $date.
+        </p>
+  </body>
+</html>
+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];
+      }
+    }
+}
+  ?>
+
+      <h2>
+        <?php
+        if ($istoday)
+        {
+          $timetext = date ("H:i:s");
+          print "Readings for today at $timetext";
+        }
+        else
+          print "Readings for $date";
+         ?>
+
+      </h2>
+      <table>
+	<?php
+      if ($istoday)                             /* include current readings */
+        makerows (<<< EOS
+-				Minimum			At				Maximum      At        Current
+Outside temperature (°C)	$min_outside_temp	$min_outside_temp_time		$max_outside_temp	$max_outside_temp_time       $outside_temperature
+Outside dewpoint (°C)	$min_outside_dewpoint	$min_outside_dewpoint_time	$max_outside_dewpoint	$max_outside_dewpoint_time		$outside_dewpoint
+Outside humidity (%)		$min_outside_humidity	$min_outside_humidity_time	$max_outside_humidity	$max_outside_humidity_time    $outside_humidity
+
+Inside temperature (°C)		$min_inside_temp	$min_inside_temp_time		$max_inside_temp	$max_inside_temp_time	$inside_temperature
+Inside dewpoint (°C)		$min_inside_dewpoint	$min_inside_dewpoint_time	$max_inside_dewpoint	$max_inside_dewpoint_time	$inside_dewpoint
+Inside humidity (%)	$min_inside_humidity	$min_inside_humidity_time	$max_inside_humidity	$max_inside_humidity_time		$inside_humidity
+
+Pressure (hPa)		$min_pressure_msl	$min_pressure_msl_time		$max_pressure_msl	$max_pressure_msl_time			$pressure_msl
+Wind speed (km/h)		$min_wind_speed		$min_wind_speed_time		$max_wind_speed		$max_wind_speed_time		$wind_speed
+Wind gust (km/h)		$min_wind_gust		$min_wind_gust_time		$max_wind_gust		$max_wind_gust_time		$wind_gust
+Wind direction (°)		$wind_direction ($wind_direction_text)
+Day's rainfall (mm)		$sum_rain
+
+EOS
+, "lrcrcr");
+	else
+	makerows (<<< EOS
+-	Minimum	At	Maximum	At
+Outside temperature (°C)	$min_outside_temp	$min_outside_temp_time	$max_outside_temp	$max_outside_temp_time
+Outside dewpoint (°C)	$min_outside_dewpoint	$min_outside_dewpoint_time	$max_outside_dewpoint	$max_outside_dewpoint_time
+Outside humidity (%)	$min_outside_humidity	$min_outside_humidity_time	$max_outside_humidity	$max_outside_humidity_time
+
+Inside temperature (°C)	$min_inside_temp	$min_inside_temp_time	$max_inside_temp	$max_inside_temp_time
+Inside dewpoint (°C)	$min_inside_dewpoint	$min_inside_dewpoint_time	$max_inside_dewpoint	$max_inside_dewpoint_time
+Inside humidity (%)	$min_inside_humidity	$min_inside_humidity_time	$max_inside_humidity	$max_inside_humidity_time
+
+Pressure (hPa)	$min_pressure_msl	$min_pressure_msl_time	$max_pressure_msl	$max_pressure_msl_time
+Wind speed (km/h)	$min_wind_speed	$min_wind_speed_time	$max_wind_speed	$max_wind_speed_time
+Wind gust (km/h)	$min_wind_gust	$min_wind_gust_time	$max_wind_gust	$max_wind_gust_time
+Day's rainfall (mm)	$sum_rain
+
+EOS
+, "lrcrc");
+?>
+	</table>
+
+	<h2>
+	Graphs for <?php echo $date ?>
+	</h2>
+        <?php
+        /*
+         * Ensure that we have the graph files.  Well, ensure that we have at least the first.
+         */
+        if ( ! file_exists ("$weatherdir/rain-$date-small.png"))
+          system ("$doplot $date 2>/dev/null >/dev/null");
+        ?>
+	<div align="left">
+	<?php showgraphs (<<< EOS
+temperatures-$date	Temperatures
+5days-$date             Five day temperatures
+humidity-$date	humidity
+wind-$date		wind
+pressure-$date		pressure
+rain-$date		rain
+BoM-Dereel-$date	BoM-Dereel
+BoM-Dereel-pressure-$date	BoM-Dereel-pressure
+EOS
+);
+?>
+
+	</div>
+    </div>
+
+<?php pagefooter ($id); ?>
--- /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 @@
+<!-- for Emacs, this is a -*- mode: html-fill; coding: utf-8 -*- document -->
+<!-- $Id: db2.php,v 1.1 2009/12/23 23:48:03 grog Exp $ -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<?php
+{
+  $title = "Dereel weather observations";
+  $subtitle = "";
+  include "header.php";
+  include "weathergraph.php";
+  $id = '$Id: db2.php,v 1.1 2009/12/23 23:48:03 grog Exp $';
+}
+?>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <?php pagetitle0 ($title); ?>
+    <meta http-equiv="refresh" content="900" ;="">
+  </head>
+
+  <body>
+    <?php pageheader0 ($title); ?>
+
+    <div align="justify">
+      <p>
+	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 <?php href
+	("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI124",
+	"Wunderground page"); ?> for this station.
+      </p>
+
+<?php
+
+{
+  $doplot = "/home/grog/src/weather/WH-1080/plots/doplots";
+  $weatherdir = "/home/grog/public_html/weather";
+  $me = basename ($_SERVER ["SCRIPT_FILENAME"]);
+
+  if (array_key_exists ("date", $_GET))
+  {
+    $mydate = validdate ($_GET ["date"], "date");
+    if (is_array ($mydate))                       /* valid date */
+      $startdate = formatdate ("Y-m-d", $mydate);
+    else
+    {
+      print <<< EOS
+        <p>
+        <font color="red">Invalid date: $mydate.  Using today's date</font>
+        </p>
+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
+        <p>
+        <font color="red">Invalid date: $myenddate.  Using $enddate</font>
+        </p>
+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
+
+     <!-- Select new date -->
+    <table>
+      <tr>
+	<td align="left">
+          <form action="$me" method="get">
+	    <input type="submit" value="Previous day"/>
+  	    <input size="20" maxlength="20" type="hidden" name="date" value="$yesterday"/>
+          </form>
+	</td>
+
+	<td>
+	  <form action="$me" method="get">
+	    <table summary="Parameter input" cellspacing="2" border="0">
+              <tr>
+		<td>
+		  <input type="submit" value="New date:"/>
+		</td>
+
+                <!-- value -->
+		<td>
+  		  <input size="20" maxlength="20" type="text" name="date" value="$startdate" />
+		</td>
+              </tr>
+	    </table>
+	  </form>
+	</td>
+EOS;
+
+    if (! $istoday)
+      print <<< EOS
+	<td align="right">
+           <form action="$me" method="get">
+	         <input type="submit" value="Next day"/>
+  	         <input size="20" maxlength="20" type="hidden" name="date" value="$tomorrow"/>
+           </form>
+	</td>
+
+	<td align="right">
+           <form action="$me" method="get">
+	         <input type="submit" value="Today"/>
+           </form>
+	</td>
+
+EOS;
+  print <<< EOS
+
+      </tr>
+    </table>
+
+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
+        <p>
+        No data found for $startdate.
+        </p>
+  </body>
+</html>
+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];
+      }
+    }
+}
+  ?>
+
+      <h2>
+        <?php
+        if ($istoday)
+        {
+          $timetext = date ("H:i:s");
+          print "Readings for today at $timetext";
+        }
+        else if ($oneday)
+          print "Readings for $startdate";
+        else
+          print "Readings for period $startdate to $enddate";
+         ?>
+
+      </h2>
+      <table>
+	<?php
+      if ($istoday)                             /* include current readings */
+        makerows (<<< EOS
+-				Current			Minimum			At				Maximum			At
+Outside temperature (°C)	$outside_temperature	$min_outside_temp	$min_outside_temp_time		$max_outside_temp	$max_outside_temp_time
+Outside dewpoint (°C)		$outside_dewpoint	$min_outside_dewpoint	$min_outside_dewpoint_time	$max_outside_dewpoint	$max_outside_dewpoint_time
+Outside humidity (%)		$outside_humidity	$min_outside_humidity	$min_outside_humidity_time	$max_outside_humidity	$max_outside_humidity_time
+
+Inside temperature (°C)		$inside_temperature	$min_inside_temp	$min_inside_temp_time		$max_inside_temp	$max_inside_temp_time
+Inside dewpoint (°C)		$inside_dewpoint	$min_inside_dewpoint	$min_inside_dewpoint_time	$max_inside_dewpoint	$max_inside_dewpoint_time
+Inside humidity (%)		$inside_humidity	$min_inside_humidity	$min_inside_humidity_time	$max_inside_humidity	$max_inside_humidity_time
+
+Pressure (hPa)			$pressure_msl		$min_pressure_msl	$min_pressure_msl_time		$max_pressure_msl	$max_pressure_msl_time
+Wind speed (km/h)		$wind_speed		$min_wind_speed		$min_wind_speed_time		$max_wind_speed		$max_wind_speed_time
+Wind gust (km/h)		$wind_gust		$min_wind_gust		$min_wind_gust_time		$max_wind_gust		$max_wind_gust_time
+Wind direction (°)		$wind_direction ($wind_direction_text)
+Day's rainfall (mm)		$sum_rain
+
+EOS
+, "lrrcrc");
+	else
+	makerows (<<< EOS
+-	Minimum	At	Maximum	At
+Outside temperature (°C)	$min_outside_temp	$min_outside_temp_time	$max_outside_temp	$max_outside_temp_time
+Outside dewpoint (°C)	$min_outside_dewpoint	$min_outside_dewpoint_time	$max_outside_dewpoint	$max_outside_dewpoint_time
+Outside humidity (%)	$min_outside_humidity	$min_outside_humidity_time	$max_outside_humidity	$max_outside_humidity_time
+
+Inside temperature (°C)	$min_inside_temp	$min_inside_temp_time	$max_inside_temp	$max_inside_temp_time
+Inside dewpoint (°C)	$min_inside_dewpoint	$min_inside_dewpoint_time	$max_inside_dewpoint	$max_inside_dewpoint_time
+Inside humidity (%)	$min_inside_humidity	$min_inside_humidity_time	$max_inside_humidity	$max_inside_humidity_time
+
+Pressure (hPa)	$min_pressure_msl	$min_pressure_msl_time	$max_pressure_msl	$max_pressure_msl_time
+Wind speed (km/h)	$min_wind_speed	$min_wind_speed_time	$max_wind_speed	$max_wind_speed_time
+Wind gust (km/h)	$min_wind_gust	$min_wind_gust_time	$max_wind_gust	$max_wind_gust_time
+Day's rainfall (mm)	$sum_rain
+
+EOS
+, "lrcrc");
+?>
+	</table>
+
+	<h2>
+	Graphs for <?php echo $startdate ?>
+	</h2>
+        <?php
+        /*
+         * Ensure that we have the graph files.  Well, ensure that we have at least the first.
+         */
+        if ($oneday)
+            $range = $startdate;
+        else
+            $range = "$startdate-$enddate";
+	if ( ! file_exists ("$weatherdir/rain-$range-small.png"))
+          {
+          if ($oneday)
+            system ("$doplot $startdate 2>/dev/null >/dev/null");
+          else
+            system ("$doplot $startdate $enddate 2>/dev/null >/dev/null");
+          }
+        ?>
+	<div align="left">
+	<?php showgraphs (<<< EOS
+outside-temp-$range	Outside temperature
+inside-temp-$range	inside-temp
+inside-outside-temp-$range	inside-outside-temp
+humidity-$range	humidity
+wind-$range		wind
+pressure-$range		pressure
+rain-$range		rain
+BoM-Dereel-$range	BoM-Dereel
+EOS
+			      , $startdate, $enddate);
+?>
+	</div>
+    </div>
+
+<?php pagefooter ($id); ?>
--- /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 @@
+<!-- for Emacs, this is a -*- mode: html-fill; coding: utf-8 -*- document -->
+<!-- $Id: index.php,v 1.14 2009/12/29 22:56:12 grog Exp grog $ -->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<?php
+   /* title should start with a lower case character */
+   $title = "Dereel weather observations";
+   $subtitle = "";
+   include "header.php";
+   include "weathergraph.php";
+   include "current.php";
+   $id = '$Id: index.php,v 1.14 2009/12/29 22:56:12 grog Exp grog $';
+   if (array_key_exists ("date", $_GET))
+    $date = $_GET ["date"];
+   else
+    $date = date ("Y-m-d");
+ ?>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <?php pagetitle0 ($title); ?>
+    <meta http-equiv="refresh" content="900" ;="">
+  </head>
+
+  <body>
+    <?php pageheader0 ($title); ?>
+
+    <div align="justify">
+      <p>
+	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 <?php href
+	("http://www.wunderground.com/weatherstation/WXDailyHistory.asp?ID=IVICTORI124",
+	"Wunderground page"); ?> for this station.
+      </p>
+
+      <h2>
+	Current readings at <?php echo $timetext ?>
+      </h2>
+
+      <table>
+	<?php makerows (<<< EOS
+-				Minimum			At				Maximum      At        Current
+Outside temperature (°C)	$min_outside_temp	$min_outside_temp_time		$max_outside_temp	$max_outside_temp_time       $outside_temperature
+Outside dewpoint (°C)	$min_outside_dewpoint	$min_outside_dewpoint_time	$max_outside_dewpoint	$max_outside_dewpoint_time		$outside_dewpoint
+Outside humidity (%)		$min_outside_humidity	$min_outside_humidity_time	$max_outside_humidity	$max_outside_humidity_time    $outside_humidity
+
+Inside temperature (°C)		$min_inside_temp	$min_inside_temp_time		$max_inside_temp	$max_inside_temp_time	$inside_temperature
+Inside dewpoint (°C)		$min_inside_dewpoint	$min_inside_dewpoint_time	$max_inside_dewpoint	$max_inside_dewpoint_time	$inside_dewpoint
+Inside humidity (%)	$min_inside_humidity	$min_inside_humidity_time	$max_inside_humidity	$max_inside_humidity_time		$inside_humidity
+
+Pressure (hPa)		$min_pressure_msl	$min_pressure_msl_time		$max_pressure_msl	$max_pressure_msl_time			$pressure_msl
+Wind speed (km/h)		$min_wind_speed		$min_wind_speed_time		$max_wind_speed		$max_wind_speed_time		$wind_speed
+Wind gust (km/h)		$min_wind_gust		$min_wind_gust_time		$max_wind_gust		$max_wind_gust_time		$wind_gust
+Wind direction (°)		$wind_direction ($wind_direction_text)
+Day's rainfall (mm)		$sum_rain
+
+EOS
+, "lrcrcr");
+?>
+      </table>
+
+      <h2>
+	Readings for <?php echo $date ?>
+      </h2>
+
+      <div align="left">
+	<?php showgraphs (<<< EOS
+temperatures-$date	Temperatures
+5days-$date             Five day temperatures
+humidity-$date           humidity
+wind-$date		wind
+pressure-$date       	pressure
+rain-$date		rain
+BoM-Dereel-$date   	BoM-Dereel
+EOS
+);
+?>
+
+      </div>
+
+      <h2>
+	Yesterday's readings <?php echo "($yesterday)"; $date = $yesterday; ?>
+      </h2>
+
+      <div align="left">
+	<?php showgraphs (<<< EOS
+outside-temp-$date      Outside temperature
+inside-temp-$date       inside-temp
+inside-outside-temp-$date  inside-outside-temp
+humidity-$date           humidity
+wind-$date		wind
+pressure-$date       	pressure
+rain-$date		rain
+BoM-Dereel-$date   	BoM-Dereel
+EOS
+);
+?>
+
+      </div>
+    </div>
+
+<?php pagefooter ($id); ?>
--- /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 *) &current_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 (&current_data [0]);
+  reend_readings (&current_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, &current_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, &current_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, &current_readings);
+      reading_time += current_readings.last_save_mins * 60; /* time this record was finished */
+      current_readings.timestamp = reading_time;
+      insert_db_row (&current_readings);
+      if (verbose)
+        print_readings (&current_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 (&current_readings);
+    if (verbose)
+      print_readings (&current_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;
+}
--- /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 <errno.h>
+#include <stdio.h>
+#include <math.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <usb.h>
+#include <mysql/mysql.h>
+#include <sys/mman.h>
+#include <pwd.h>
+
+/* 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 *);
--- /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 <endian.h>
+#include <asm/byteorder.h>
+#define htobe16 __cpu_to_be16
+#define le16toh __le16_to_cpu
+#include <time.h>
+#else
+#include <sys/endian.h>
+#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;
+
--- /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
--- /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;
+}
--- /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,
+             "<?php\n"
+             "/* Automatically generated file.  Do not edit */\n"
+             "\t$timetext = \"%s\";\n"
+             "\t$yesterday = \"%s\";\n"
+             "\t$timestamp = %d;\n",
+             date_time,                         /* time in text form */
+             yesterday_text,                    /* yesterday's date YYYY-MM-DD */
+             (int) now );                       /* time of reading */
+
+    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"
+             "       SUM(rain)\n"
+             "FROM %s\n"
+             "WHERE unix_timestamp(timestamp(date,time)) >= %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;
+}