Mercurial > ~darius > hgwebdir.cgi > wh1080
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 *) ¤t_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 (¤t_data [0]); | |
326 reend_readings (¤t_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, ¤t_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, ¤t_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, ¤t_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 (¤t_readings); | |
557 if (verbose) | |
558 print_readings (¤t_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 (¤t_readings); | |
620 if (verbose) | |
621 print_readings (¤t_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 } |