comparison wh1080.c @ 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 9da35e705144
comparison
equal deleted inserted replaced
-1:000000000000 0:9dab44dcb331
1 /*
2 * Data input utility for Fine Offset WH-1080 weather station.
3 * This bases on code supplied to me by Steve Woodford.
4 *
5 * Greg Lehey, 12 November 2009
6 *
7 * $Id: wh1080.c,v 1.18 2010/02/07 03:40:37 grog Exp $
8 */
9
10 #include "wh1080.h"
11 struct usb_device *station_device;
12 usb_dev_handle *station;
13
14 /* Data from the device */
15 struct wh1080_page0 page0;
16 struct wh1080_page1 page1;
17 struct wh1080_readings current_data [2]; /* current data readings, from station */
18
19 struct readings current_readings; /* current data readings, our version */
20 struct readings previous_readings; /* copy of previous set of readings */
21
22 char previous_wind_direction_text [4]; /* reuse previous direction if we have no wind */
23
24 int previous_rain; /* previous rainfall reading: we need a difference */
25 float db_previous_rain; /* previous rainfall reading for database */
26
27 struct libusb_context **usb_context;
28
29 #if BYTE_ORDER == BIG_ENDIAN
30 /*
31 * The station sends data in big-endian format. If we're running on a
32 * big-endian machine, we need to turn the 16 bit fields around.
33 */
34
35 void reend_page0 ()
36 {
37 page0.current_page = le16toh (page0.current_page);
38 }
39
40 void reend_page1 ()
41 {
42 page1.rel_pressure = le16toh (page1.rel_pressure);
43 page1.abs_pressure = le16toh (page1.abs_pressure);
44 }
45
46 void reend_readings (struct wh1080_readings *page)
47 {
48 page->inside_temp = le16toh (page->inside_temp); /* inside temperature */
49 page->outside_temp = le16toh (page->outside_temp); /* outside temperature */
50 page->pressure = le16toh (page->pressure); /* absolute pressure, hPa */
51 page->rain = le16toh (page->rain); /* rainfall in 0.3 mm units */
52 }
53 #endif
54
55 /*
56 * Scan USB busses for our device.
57 * Return 1 and device information to devp if found.
58 */
59 int find_usb_device (int vendor, int product, struct usb_device **devp)
60 {
61 struct usb_bus *bus;
62 int count;
63
64 count = usb_find_busses ();
65 count = usb_find_devices ();
66
67 for (bus = usb_get_busses (); bus; bus = bus->next)
68 {
69 for (*devp = bus->devices; *devp; *devp = (*devp)->next)
70 {
71 if ((*devp)->descriptor.idVendor == vendor
72 && (*devp)->descriptor.idProduct == product )
73 return 1;
74 }
75 }
76 return 0; /* not found */
77 }
78
79 /*
80 * Set up communications with the weather station.
81 * On error, print message and die. Return means success.
82 */
83 void device_setup ()
84 {
85 usb_init (); /* initialize libusb */
86
87 if (! find_usb_device (WH1080_USB_VENDOR, WH1080_USB_PRODUCT, &station_device))
88 {
89 fprintf (stderr, "Can't find WH-1080 device\n");
90 exit (1);
91 }
92
93 /* Open station */
94 if (! (station = usb_open (station_device)))
95 {
96 fprintf (stderr,
97 "Can't open weather station: %s (%d)\n",
98 usb_strerror (),
99 errno);
100 exit (1);
101 }
102
103 #ifdef linux
104 /*
105 * For some reason, Linux gives a device to the kernel, so we need
106 * to prise it away again.
107 */
108 #if 0
109 http://blemings.org/hugh/blog/blosxom.cgi/2008/01/15#20080115a
110 struct usb_bus *bus_list;
111 struct usb_device *dev = NULL;
112 struct usb_dev_handle *handle;
113
114
115 /* Look for the first u4xx device we can find then try and open */
116 if((dev = find_u4xx(bus_list)) == NULL) {
117 return NULL;
118 }
119
120 /* Try and get a handle to the device */
121 if((handle = usb_open(dev)) == NULL) {
122 return NULL;
123 }
124
125 /* The kernel's HID driver will seize the USBMicro device as it
126 says it's a HID device - we need to tell the kernel to
127 let go of it */
128 if (usb_detach_kernel_driver_np(handle, 0) < 0) {
129 /* If this fails, usually just means that no kernel driver
130 had attached itself to the device so just ignore/warn */
131 }
132
133 /* Set the configuration */
134 if(usb_set_configuration(handle, 1) != 0) {
135 usb_close(handle);
136 return NULL;
137 }
138
139 /* Clain interface - gather would need to this for each
140 interface if the device has more than one */
141 if (usb_claim_interface(handle, 0) != 0) {
142 usb_close(handle);
143 return NULL;
144 }
145
146 /* etc. etc. */
147
148 #endif
149 if (usb_detach_kernel_driver_np (station, 0) < 0)
150 {
151 fprintf (stderr, "%s (%d)\n", usb_strerror (), errno);
152 /* exit (1); */
153 }
154 #endif
155
156 /* And grab the interface */
157 if (usb_claim_interface (station, 0) < 0)
158 {
159 fprintf (stderr, "%s (%d)\n", usb_strerror (), errno);
160 exit (1);
161 }
162 }
163
164 /*
165 * Try to recover from USB breakage.
166 * XXX This doesn't work in the current form.
167 */
168 void device_reset ()
169 {
170 if (usb_release_interface (station, 0) < 0)
171 fprintf (stderr,
172 "Can't release interface: %s (%d)\n",
173 usb_strerror (),
174 errno);
175 #if 0
176 if (usb_close (station) < 0)
177 fprintf (stderr,
178 "Can't close interface: %s (%d)\n",
179 usb_strerror (),
180 errno);
181 device_setup ();
182 #endif
183 }
184
185
186 /*
187 * Write control data to device. We don't write real data.
188 */
189 int write_station_control (char *buf)
190 {
191 int written;
192 char textdate [64];
193
194 /* XXX Find out what all this means, and be cleverer about retries */
195 do
196 {
197 if (written = (usb_control_msg (station,
198 USB_TYPE_CLASS + USB_RECIP_INTERFACE,
199 0x9,
200 0x200,
201 0,
202 buf,
203 8, /* we always write 8 bytes */
204 WH1080_WRITE_TIMEOUT) > 0))
205 return written;
206 #if 0
207 if (errno == ENOTTY)
208 {
209 fprintf (stderr, "USB bus stuck, reinitializing\n");
210 device_reset ();
211 }
212 #endif
213 }
214 while (errno == EINTR); /* || (errno == ENOTTY)); */
215
216 datetext (time (NULL), textdate, "%e %B %Y %T");
217 fprintf (stderr,
218 "%s: PID %d: can't write to device: %s (%d)\n",
219 textdate,
220 getpid (),
221 usb_strerror (),
222 errno );
223 return 0;
224 }
225
226 /*
227 * Read 8 bytes from the device.
228 */
229 int read_station (char *buf)
230 {
231 int bytes_read;
232
233 /* XXX Find out what all this means, and be cleverer about retries */
234 do
235 {
236 if ((bytes_read = usb_interrupt_read (station,
237 USB_ENDPOINT_IN | USB_RECIP_INTERFACE,
238 buf,
239 8,
240 10 )) == 8 )
241 return bytes_read;
242 if (errno == EAGAIN)
243 usleep (10000);
244 }
245 while (errno != EINTR);
246 fprintf (stderr,
247 "Can't read device: %s (%d)\n",
248 usb_strerror (),
249 errno );
250 return 0;
251 }
252
253 /* Read a page (32 bytes) from the device. */
254 int read_station_page (uint16_t page, char *buf)
255 {
256 int bytes_read = 0; /* keep track of input */
257 struct read_page_request
258 {
259 char command; /* XXX I'm guessing at this */
260 uint16_t address;
261 char length;
262 } __attribute__ ((packed)) request [2];
263
264 #if BYTE_ORDER == LITTLE_ENDIAN
265 page = htobe16 (page); /* in big-endian for the command */
266 #endif
267 request [0] = (struct read_page_request) {0xa1, page, 32};
268 request [1] = request [0];
269 if (write_station_control ((char *) request) == 0)
270 exit (1); /* XXX we've already complained */
271 while (bytes_read < 32)
272 bytes_read += read_station ((char *) &buf [bytes_read]);
273 return bytes_read;
274 }
275
276 /*
277 * Read and compare page from station. Keep trying until we get two that are
278 * the same.
279 *
280 * XXX don't loop for ever.
281 */
282 int read_valid_station_page (uint16_t page, char *buf)
283 {
284 char duplicate [WH1080_PAGE_SIZE];
285
286 read_station_page (page, duplicate); /* read once for comparison */
287 while (1)
288 {
289 read_station_page (page, buf); /* and once where we want it */
290 if (! memcmp (buf, duplicate, WH1080_PAGE_SIZE)) /* bingo! */
291 return WH1080_PAGE_SIZE;
292 memcpy (duplicate, buf, WH1080_PAGE_SIZE);
293 }
294 }
295
296 /* Read observations from specified page.
297 *
298 * We have to read 32 bytes from the device, but the data we want is only 16
299 * bytes. If it's the last 16 bytes in memory, who knows what will happen? To
300 * be on the safe side, always read from an even-numbered page and then return 0
301 * or 1 to point to the correct entry in (global) current_data.
302 */
303 void read_readings (int page, struct readings *readings)
304 {
305 int pagehalf = (page & 0x10) >> 4; /* index in our two entries */
306
307 #if 0
308 /* Page 1: pressure readings, currently unused */
309 read_valid_station_page (WH1080_PAGE1, (char *) &page1);
310 #if BYTE_ORDER == BIG_ENDIAN
311 /* reend_page0 (); */
312 reend_page1 ();
313 #endif
314 #endif
315
316 /* Read the data itself */
317 read_valid_station_page (page & WH1080_PAGE_MASK, (char *) &current_data);
318
319 #if 0
320 printf ("\n");
321 hexdump ((unsigned char *) current_data, 2 * sizeof (struct wh1080_readings));
322 #endif
323 #if BYTE_ORDER == BIG_ENDIAN
324 /* Turn both around to avoid surprises */
325 reend_readings (&current_data [0]);
326 reend_readings (&current_data [1]);
327 #endif
328
329 /*
330 * Now copy the info back to readings. Note that we don't set the timestamp
331 * here; only the caller knows when this reading was made.
332 */
333 readings->page = page; /* save page number too */
334 readings->last_save_mins
335 = current_data [pagehalf].last_save_mins; /* last save minutes (?) */
336 readings->inside_humidity
337 = current_data [pagehalf].inside_humidity; /* humidity in percent */
338 /* This appears to be an "out of range" situation. */
339 if (readings->inside_humidity == 255)
340 readings->inside_humidity = previous_readings.inside_humidity;
341 readings->inside_temp
342 = ((float) current_data [pagehalf].inside_temp) / 10; /* inside temperature */
343 readings->outside_humidity
344 = current_data [pagehalf].outside_humidity; /* humidity in percent */
345 if (readings->outside_humidity == 255)
346 readings->outside_humidity = previous_readings.outside_humidity;
347 readings->outside_temp
348 = ((float) current_data [pagehalf].outside_temp) / 10; /* outside temperature */
349 readings->pressure
350 = ((float) current_data [pagehalf].pressure) / 10 /* absolute pressure, hPa */
351 - config.pressure_error; /* adjust for inaccuracies */
352 readings->wind_speed
353 = ((float) current_data [pagehalf].wind_speed) / 3.6; /* wind speed in km/h */
354 readings->wind_gust
355 = ((float) current_data [pagehalf].wind_gust) / 3.6; /* wind gust speed in km/h */
356 /*
357 * Wind direction is confusing. It's normally a value between 0 and 15, but
358 * it's 0x80 if there's no wind at all. Following an idea by Steve Woodford,
359 * reuse the previous value if it's 0x80.
360 *
361 * Other values shouldn't happen. If they do, report them in numeric form.
362 *
363 * In addition to the text, we save the original value in degrees for
364 * Wunderground and friends. If it's invalid, we simply don't send it.
365 */
366 if (current_data [pagehalf].wind_direction == 0x80) /* no wind */
367 {
368 strcpy (readings->wind_direction_text, previous_wind_direction_text);
369 readings->wind_direction = INVALID_DIRECTION;
370 }
371 else if (current_data [pagehalf].wind_direction < 16) /* valid direction */
372 {
373 strcpy (previous_wind_direction_text, wind_directions [current_data [pagehalf].wind_direction]);
374 strcpy (readings->wind_direction_text, wind_directions [current_data [pagehalf].wind_direction]);
375 readings->wind_direction = ((float) current_data [pagehalf].wind_direction) * 22.5;
376 }
377 else /* invalid value */
378 {
379 sprintf (readings->wind_direction_text,
380 "%3d",
381 current_data [pagehalf].wind_direction); /* just put in the number */
382 readings->wind_direction = INVALID_DIRECTION;
383 }
384
385 /* Count incremental rainfall in readings->rain */
386 if (current_data [pagehalf].rain != previous_rain)
387 {
388 int temprain = current_data [pagehalf].rain - previous_rain;
389
390 if (temprain < 0) /* wraparound */
391 temprain += USHRT_MAX; /* add 65536 */
392 readings->rain += ((float) temprain) * 0.3; /* rainfall in mm */
393 previous_rain = current_data [pagehalf].rain;
394 }
395
396 set_dewpoints (readings);
397 set_sea_level_pressure (readings);
398
399 /* Stuff from page 1 */
400 readings->page1_abs_pressure = ((float) page1.abs_pressure) / 10; /* absolute pressure, hPa */
401 }
402
403 /*
404 * Read page 0.
405 */
406
407 void read_page0 ()
408 {
409 /*
410 * Page 0: magic and offset of current page
411 */
412 do
413 read_valid_station_page (WH1080_PAGE0, (char *) &page0);
414 while (((page0.magic != WH1080_PAGE0_MAGIC1A)
415 && (page0.magic != WH1080_PAGE0_MAGIC1B) )
416 || (page0.magic2 != WH1080_PAGE0_MAGIC2) );
417 }
418
419 /*
420 * Get a set of readings from the station.
421 */
422 void read_station_data ()
423 {
424 read_page0 (); /* get current page number from page0 */
425 read_readings (page0.current_page, &current_readings);
426 current_readings.timestamp = time (NULL);
427 }
428
429 void dump_memory ()
430 {
431 int page;
432 struct readings readings;
433 time_t reading_time; /* calculate time of the reading */
434 struct tm *reading_tm; /* for trimming off seconds */
435
436 /*
437 * Calculate the time of the most recent archive reading, which is
438 * current_readings.last_save_mins old. This is confused by the fact that the
439 * times don't include seconds, so for some semblence of uniformity, assume
440 * that we're in the middle of the minute.
441 */
442 reading_time = time (NULL); /* now */
443 reading_tm = localtime (&reading_time); /* and in struct tm format */
444 reading_time -= reading_tm->tm_sec; /* adjust to beginning of the minute */
445 /* XXX decide how to round */
446
447 /*
448 * Note that there's a race condition in the end condition.
449 * page0.current_page could increment during dumping. That's perfectly
450 * acceptable.
451 */
452 for (page = page0.current_page + WH1080_ARCHIVE_RECORD_SIZE;
453 page != page0.current_page;
454 page += WH1080_ARCHIVE_RECORD_SIZE)
455 {
456 if (page >= 0xfff0) /* wrap around */
457 page = WH1080_FIRST_ARCHIVE; /* back to last one in memory */
458 read_readings (page, &readings);
459 #if 0
460 if (readings.last_save_mins != 30) /* this seems to always be the value in archive readings */
461 return; /* this would be the beginning of time? */
462 #endif
463 readings.timestamp = reading_time;
464 print_readings (&readings);
465 reading_time += WH1080_ARCHIVE_INTERVAL;
466 }
467 }
468
469 void insert_db_row (struct readings *readings)
470 {
471 char reading_date [STAMPSIZE]; /* formatted date */
472 char reading_time [STAMPSIZE]; /* and time */
473
474 strftime (reading_date, STAMPSIZE, "%F", localtime (&readings->timestamp)); /* format time and date */
475 strftime (reading_time, STAMPSIZE, "%T", localtime (&readings->timestamp)); /* format time and date */
476
477 sprintf (mysql_querytext,
478 "INSERT INTO %s\n"
479 " (station_id, date, time, inside_humidity, inside_temp, inside_dewpoint,\n"
480 " outside_humidity, outside_temp, outside_dewpoint, pressure_abs, pressure_msl,\n"
481 " wind_speed, wind_gust, wind_direction, wind_direction_text, rain)\n"
482 "VALUES\n"
483 " (\"%s\", \"%s\", \"%s\", %d, %6.1f, %6.1f, "
484 " %d, %6.1f, %6.1f, %6.1f, %6.1f, "
485 " %6.1f, %6.1f, %6.1f, \"%s\", %6.1f);",
486 config.db_table,
487 config.station_id,
488 reading_date,
489 reading_time,
490 readings->inside_humidity,
491 readings->inside_temp,
492 readings->inside_dewpoint,
493 readings->outside_humidity,
494 readings->outside_temp,
495 readings->outside_dewpoint,
496 readings->pressure,
497 readings->pressure_sea_level,
498 readings->wind_speed,
499 readings->wind_gust,
500 readings->wind_direction,
501 readings->wind_direction_text,
502 readings->rain - db_previous_rain );
503 db_previous_rain = readings->rain; /* update our current rainfall XXX fix this */
504 if (update) /* only if updates set */
505 {
506 if (mysql_query (mysql, mysql_querytext))
507 {
508 fprintf (stderr,
509 "Can't insert database record: %s (%d)\n",
510 mysql_error (mysql),
511 mysql_errno (mysql) );
512 }
513 }
514 else if (verbose)
515 puts (mysql_querytext);
516 }
517
518 /*
519 * Check if we have missed any updates since we last ran.
520 *
521 * Not so affectionately named after Powercor (http://www.powercor.com.au/),
522 * whose continual power outages are the main reason that this function is
523 * needed.
524 *
525 * XXX check this for relationship to share file.
526 */
527 void recover_powercor_breakage ()
528 {
529 int page;
530 time_t reading_time;
531
532 read_page0 ();
533 if (current_readings.page != page0.current_page) /* we've moved on */
534 {
535 /* Go back to the previous record to read rain */
536 if ((page = current_readings.page - WH1080_ARCHIVE_RECORD_SIZE)
537 < WH1080_FIRST_ARCHIVE) /* wrap around */
538 page = WH1080_LAST_ARCHIVE;
539 reading_time = current_readings.timestamp /* time of this reading */
540 - (current_readings.last_save_mins * 60);
541 read_readings (page, &current_readings);
542 do
543 {
544 /* Next page, with wraparound */
545 page += WH1080_ARCHIVE_RECORD_SIZE;
546 if (page > WH1080_LAST_ARCHIVE)
547 page = WH1080_FIRST_ARCHIVE;
548 /*
549 * The archive records appear to be every 30 minutes, but since the
550 * duration is stored in them, there's no reason not to calculate the
551 * time the record was completed.
552 */
553 read_readings (page, &current_readings);
554 reading_time += current_readings.last_save_mins * 60; /* time this record was finished */
555 current_readings.timestamp = reading_time;
556 insert_db_row (&current_readings);
557 if (verbose)
558 print_readings (&current_readings);
559 }
560 while (page != page0.current_page);
561 }
562 }
563
564 void usage (char *me)
565 {
566 fprintf (stderr,
567 "Usage: %s [-d] [-n] [-v] [station ID] [db user] [password] [db host] [database]\n",
568 me);
569 exit (1);
570 }
571
572
573 int main (int argc, char *argv [])
574 {
575 read_config (argc, argv);
576
577 device_setup (); /* initialize the device */
578 if (recover)
579 recover_powercor_breakage (); /* check for missed updates */
580 read_station_data (); /* at least to set the rainfall counter */
581
582 /*
583 * Rainfall is a pain. We have several ways of reporting it:
584 *
585 * Print out from this program.
586 * Print out in various forms from report.
587 * Insert into database.
588 * Inform Wunderground.
589 *
590 * Each of these can be done asynchronously, so we maintain cumulative values of
591 * current rainfall and a rainfall value for each of these reporting methods.
592 * They are:
593 *
594 * Print out from this program (really util.c)
595 * current_readings.rain - print_previous_rain
596 * Inform Wunderground.
597 * current_readings.rain - current_readings.previous_rain
598 * Print out from report
599 * This is done from util.c as well, so we need to set print_previous_rain
600 * to current_readings.previous_rain
601 * Insert into database.
602 * Done from wh1080,
603 * current_readings.rain - db_previous_rain
604 */
605 current_readings.rain = 0.0; /* And current rainfall */
606 current_readings.previous_rain = 0.0; /* Initialize previous rainfall for report */
607 print_previous_rain = 0.0;
608 db_previous_rain = 0.0;
609
610 if (debug)
611 {
612 dump_memory (); /* show old data */
613 exit (0);
614 }
615
616 while (1)
617 {
618 read_station_data ();
619 insert_db_row (&current_readings);
620 if (verbose)
621 print_readings (&current_readings);
622
623 /* XXX calculate the exact time to wait, which will be marginally smaller */
624 sleep (config.poll_interval);
625 previous_readings = current_readings; /* make a copy of the current readings */
626 }
627 /* If we ever get here, this is what we should do */
628 usb_release_interface (station, 0);
629 return 0;
630 }