Mercurial > ~darius > hgwebdir.cgi > wh1080
view wh1080.c @ 4:f561c7f130a0
Make -n not do any DB stuff so we don't need to #if the MySQL stuff out.
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Thu, 11 Feb 2010 11:47:11 +1030 |
parents | dc5ff2a1ed81 |
children | b22a888eb975 |
line wrap: on
line source
/* * 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 <stdlib.h> #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) { #define MAXTRIES 100 int bytes_read, i = 0; /* 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 ){ if (i > 5) fprintf(stderr, "Read OK after %d tries\n", i + 1); return bytes_read; } if (errno == EAGAIN) usleep (10000); i++; } while (errno != EINTR && i < MAXTRIES); if (i == MAXTRIES) { #if 0 fprintf(stderr, "Can't read from device after %d attempts\n", MAXTRIES); #endif } else 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 i, bytes_read, tries = 0; 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 rerequest: bytes_read = 0; tries++; if (tries > 10) { fprintf(stderr, "Unable to read page after 10 tries\n"); exit(1); } 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) { if ((i = read_station ((char *) &buf [bytes_read])) == 0) { #if 0 fprintf(stderr, "Couldn't read from the device, rerequesting\n"); #endif goto rerequest; } bytes_read += i; } return bytes_read; } /* * Read and compare page from station. Keep trying until we get two that are * the same. * * XXX don't loop for ever. */ int read_valid_station_page (uint16_t page, char *buf) { char duplicate [WH1080_PAGE_SIZE]; read_station_page (page, duplicate); /* read once for comparison */ while (1) { read_station_page (page, buf); /* and once where we want it */ if (! memcmp (buf, duplicate, WH1080_PAGE_SIZE)) /* bingo! */ return WH1080_PAGE_SIZE; memcpy (duplicate, buf, WH1080_PAGE_SIZE); } } /* Read observations from specified page. * * We have to read 32 bytes from the device, but the data we want is only 16 * bytes. If it's the last 16 bytes in memory, who knows what will happen? To * be on the safe side, always read from an even-numbered page and then return 0 * or 1 to point to the correct entry in (global) current_data. */ void read_readings (int page, struct readings *readings) { int pagehalf = (page & 0x10) >> 4; /* index in our two entries */ #if 0 /* Page 1: pressure readings, currently unused */ read_valid_station_page (WH1080_PAGE1, (char *) &page1); #if BYTE_ORDER == BIG_ENDIAN /* reend_page0 (); */ reend_page1 (); #endif #endif /* Read the data itself */ read_valid_station_page (page & WH1080_PAGE_MASK, (char *) ¤t_data); #if 0 printf ("\n"); hexdump ((unsigned char *) current_data, 2 * sizeof (struct wh1080_readings)); #endif #if BYTE_ORDER == BIG_ENDIAN /* Turn both around to avoid surprises */ reend_readings (¤t_data [0]); reend_readings (¤t_data [1]); #endif /* * Now copy the info back to readings. Note that we don't set the timestamp * here; only the caller knows when this reading was made. */ readings->page = page; /* save page number too */ readings->last_save_mins = current_data [pagehalf].last_save_mins; /* last save minutes (?) */ readings->inside_humidity = current_data [pagehalf].inside_humidity; /* humidity in percent */ /* This appears to be an "out of range" situation. */ if (readings->inside_humidity == 255) readings->inside_humidity = previous_readings.inside_humidity; readings->inside_temp = ((float) current_data [pagehalf].inside_temp) / 10; /* inside temperature */ readings->outside_humidity = current_data [pagehalf].outside_humidity; /* humidity in percent */ if (readings->outside_humidity == 255) readings->outside_humidity = previous_readings.outside_humidity; readings->outside_temp = ((float) current_data [pagehalf].outside_temp) / 10; /* outside temperature */ readings->pressure = ((float) current_data [pagehalf].pressure) / 10 /* absolute pressure, hPa */ - config.pressure_error; /* adjust for inaccuracies */ readings->wind_speed = ((float) current_data [pagehalf].wind_speed) / 3.6; /* wind speed in km/h */ readings->wind_gust = ((float) current_data [pagehalf].wind_gust) / 3.6; /* wind gust speed in km/h */ /* * Wind direction is confusing. It's normally a value between 0 and 15, but * it's 0x80 if there's no wind at all. Following an idea by Steve Woodford, * reuse the previous value if it's 0x80. * * Other values shouldn't happen. If they do, report them in numeric form. * * In addition to the text, we save the original value in degrees for * Wunderground and friends. If it's invalid, we simply don't send it. */ if (current_data [pagehalf].wind_direction == 0x80) /* no wind */ { strcpy (readings->wind_direction_text, previous_wind_direction_text); readings->wind_direction = INVALID_DIRECTION; } else if (current_data [pagehalf].wind_direction < 16) /* valid direction */ { strcpy (previous_wind_direction_text, wind_directions [current_data [pagehalf].wind_direction]); strcpy (readings->wind_direction_text, wind_directions [current_data [pagehalf].wind_direction]); readings->wind_direction = ((float) current_data [pagehalf].wind_direction) * 22.5; } else /* invalid value */ { sprintf (readings->wind_direction_text, "%3d", current_data [pagehalf].wind_direction); /* just put in the number */ readings->wind_direction = INVALID_DIRECTION; } /* Count incremental rainfall in readings->rain */ if (current_data [pagehalf].rain != previous_rain) { int temprain = current_data [pagehalf].rain - previous_rain; if (temprain < 0) /* wraparound */ temprain += USHRT_MAX; /* add 65536 */ readings->rain += ((float) temprain) * 0.3; /* rainfall in mm */ previous_rain = current_data [pagehalf].rain; } set_dewpoints (readings); set_sea_level_pressure (readings); /* Stuff from page 1 */ readings->page1_abs_pressure = ((float) page1.abs_pressure) / 10; /* absolute pressure, hPa */ } /* * Read page 0. */ void read_page0 () { /* * Page 0: magic and offset of current page */ do read_valid_station_page (WH1080_PAGE0, (char *) &page0); while (((page0.magic != WH1080_PAGE0_MAGIC1A) && (page0.magic != WH1080_PAGE0_MAGIC1B) ) || (page0.magic2 != WH1080_PAGE0_MAGIC2) ); } /* * Get a set of readings from the station. */ void read_station_data () { read_page0 (); /* get current page number from page0 */ read_readings (page0.current_page, ¤t_readings); current_readings.timestamp = time (NULL); } void dump_memory () { int page; struct readings readings; time_t reading_time; /* calculate time of the reading */ struct tm *reading_tm; /* for trimming off seconds */ /* * Calculate the time of the most recent archive reading, which is * current_readings.last_save_mins old. This is confused by the fact that the * times don't include seconds, so for some semblence of uniformity, assume * that we're in the middle of the minute. */ reading_time = time (NULL); /* now */ reading_tm = localtime (&reading_time); /* and in struct tm format */ reading_time -= reading_tm->tm_sec; /* adjust to beginning of the minute */ /* XXX decide how to round */ /* * Note that there's a race condition in the end condition. * page0.current_page could increment during dumping. That's perfectly * acceptable. */ for (page = page0.current_page + WH1080_ARCHIVE_RECORD_SIZE; page != page0.current_page; page += WH1080_ARCHIVE_RECORD_SIZE) { if (page >= 0xfff0) /* wrap around */ page = WH1080_FIRST_ARCHIVE; /* back to last one in memory */ read_readings (page, &readings); #if 0 if (readings.last_save_mins != 30) /* this seems to always be the value in archive readings */ return; /* this would be the beginning of time? */ #endif readings.timestamp = reading_time; print_readings (&readings); reading_time += WH1080_ARCHIVE_INTERVAL; } } void insert_db_row (struct readings *readings) { char reading_date [STAMPSIZE]; /* formatted date */ char reading_time [STAMPSIZE]; /* and time */ strftime (reading_date, STAMPSIZE, "%F", localtime (&readings->timestamp)); /* format time and date */ strftime (reading_time, STAMPSIZE, "%T", localtime (&readings->timestamp)); /* format time and date */ sprintf (mysql_querytext, "INSERT INTO %s\n" " (station_id, date, time, inside_humidity, inside_temp, inside_dewpoint,\n" " outside_humidity, outside_temp, outside_dewpoint, pressure_abs, pressure_msl,\n" " wind_speed, wind_gust, wind_direction, wind_direction_text, rain)\n" "VALUES\n" " (\"%s\", \"%s\", \"%s\", %d, %6.1f, %6.1f, " " %d, %6.1f, %6.1f, %6.1f, %6.1f, " " %6.1f, %6.1f, %6.1f, \"%s\", %6.1f);", config.db_table, config.station_id, reading_date, reading_time, readings->inside_humidity, readings->inside_temp, readings->inside_dewpoint, readings->outside_humidity, readings->outside_temp, readings->outside_dewpoint, readings->pressure, readings->pressure_sea_level, readings->wind_speed, readings->wind_gust, readings->wind_direction, readings->wind_direction_text, readings->rain - db_previous_rain ); db_previous_rain = readings->rain; /* update our current rainfall XXX fix this */ #if 0 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) #endif } /* * Check if we have missed any updates since we last ran. * * Not so affectionately named after Powercor (http://www.powercor.com.au/), * whose continual power outages are the main reason that this function is * needed. * * XXX check this for relationship to share file. */ void recover_powercor_breakage () { int page; time_t reading_time; read_page0 (); if (current_readings.page != page0.current_page) /* we've moved on */ { /* Go back to the previous record to read rain */ if ((page = current_readings.page - WH1080_ARCHIVE_RECORD_SIZE) < WH1080_FIRST_ARCHIVE) /* wrap around */ page = WH1080_LAST_ARCHIVE; reading_time = current_readings.timestamp /* time of this reading */ - (current_readings.last_save_mins * 60); read_readings (page, ¤t_readings); do { /* Next page, with wraparound */ page += WH1080_ARCHIVE_RECORD_SIZE; if (page > WH1080_LAST_ARCHIVE) page = WH1080_FIRST_ARCHIVE; /* * The archive records appear to be every 30 minutes, but since the * duration is stored in them, there's no reason not to calculate the * time the record was completed. */ read_readings (page, ¤t_readings); reading_time += current_readings.last_save_mins * 60; /* time this record was finished */ current_readings.timestamp = reading_time; insert_db_row (¤t_readings); if (verbose) print_readings (¤t_readings); } while (page != page0.current_page); } } void usage (char *me) { fprintf (stderr, "Usage: %s [-d] [-n] [-v] [station ID] [db user] [password] [db host] [database]\n", me); exit (1); } int main (int argc, char *argv []) { read_config (argc, argv); device_setup (); /* initialize the device */ if (recover) recover_powercor_breakage (); /* check for missed updates */ read_station_data (); /* at least to set the rainfall counter */ /* * Rainfall is a pain. We have several ways of reporting it: * * Print out from this program. * Print out in various forms from report. * Insert into database. * Inform Wunderground. * * Each of these can be done asynchronously, so we maintain cumulative values of * current rainfall and a rainfall value for each of these reporting methods. * They are: * * Print out from this program (really util.c) * current_readings.rain - print_previous_rain * Inform Wunderground. * current_readings.rain - current_readings.previous_rain * Print out from report * This is done from util.c as well, so we need to set print_previous_rain * to current_readings.previous_rain * Insert into database. * Done from wh1080, * current_readings.rain - db_previous_rain */ current_readings.rain = 0.0; /* And current rainfall */ current_readings.previous_rain = 0.0; /* Initialize previous rainfall for report */ print_previous_rain = 0.0; db_previous_rain = 0.0; if (debug) { dump_memory (); /* show old data */ exit (0); } while (1) { read_station_data (); insert_db_row (¤t_readings); if (verbose) print_readings (¤t_readings); /* XXX calculate the exact time to wait, which will be marginally smaller */ sleep (config.poll_interval); previous_readings = current_readings; /* make a copy of the current readings */ } /* If we ever get here, this is what we should do */ usb_release_interface (station, 0); return 0; } /* *;;; Local Variables: *** *;;; c-basic-offset:2 *** *;;; End: *** */