1 /******************************************************************
2 *
3 *
4 * Copyright (C) 2004 Bruce Bennett <bruts@adelphia.net>
5 * Portions Copyright (C) 2000-2019 The Xastir Group
6 *
7 * (see the files README and COPYING for more details)
8 *
9 * This file implements all of the database to APRS daemon.
10 *
11 *
12
13 Davis/Data Base Weather --> APRS Weather
14
15 Intended use:
16
17 Create & provide APRS style packet string
18 without position information from MySQL database
19 weather information stored there by meteo-0.9.4
20 (See http://meteo.othello.ch for source) to
21 xastir-1.2.1 (See http://www.xastir.org for source)
22
23 Note: "meteo-0.9.x" is a weather data accumulator
24 aimed at Davis weather stations, which stores weather
25 data in a mysql database. It is configured in two
26 places, an XML file (default name meteo.xml) and in
27 the database named in the XML file (default database
28 name is "meteo")
29
30 Output is to the ip hostname:port required in the
31 command line.
32
33 ACKNOWLEGEMENTS:
34
35 Elements of this software are taken from wx200d ver 1.2
36 by Tim Witham <twitham@quiknet.com>, and it is modeled
37 after that application.
38
39 *******************************************************************/
40 #include <config.h>
41 #include <defs.h>
42 #include <stdio.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sys/socket.h>
47 #include <sys/stat.h>
48 #include <netinet/in.h>
49 #include <arpa/inet.h>
50 #include <netdb.h>
51 #include <signal.h>
52 #include <syslog.h>
53 #include <errno.h>
54 #include <getopt.h>
55 #include <mysql.h>
56
57 #define MAXARGS 20 /* maximum CGI args to parse */
58 #define TMPLEN 128 /* max length of CGI */
59 #define BUFLEN 32 /* max length of hostname:port */
60
61 #define POLL_INTERVAL 90 // default polling interval
62
63 #define VALID_WINDDIR 0x001
64 #define VALID_WINDSPD 0x002
65 #define VALID_WINDGST 0x004
66 #define VALID_TEMP 0x008
67 #define VALID_RAIN 0x010
68 #define VALID_RAIN24H 0x020
69 #define VALID_HUMIDITY 0x040
70 #define VALID_AIRPRESS 0x080
71 #define VALID_RAINDAY 0x100
72
73 #define MTPS2MPH 2.2369
74 #define DEGC2DEGF 1.8
75 #define MM2IN100TH 3.937
76 #define INHG2HPA10TH 338.638
77
78 #define OUTDOOR_SENSOR 1
79
80 //---From the static table "mfield", which really should be dynamically read here---
81 // (but then I couldn't use a switch statement)
82
83 #define TEMPERATURE 0
84 #define TEMPERATURE_MIN 1
85 #define TEMPERATURE_MAX 2
86 #define HUMIDITY 10
87 #define HUMIDITY_MIN 11
88 #define HUMIDITY_MAX 12
89 #define WETNESS 20
90 #define WETNESS_MIN 21
91 #define WETNESS_MAX 22
92 #define AIR_PRESSURE 30
93 #define AIR_PRESSURE_MIN 31
94 #define AIR_PRESSURE_MAX 32
95 #define SOLAR 40
96 #define UV 41
97 #define RAIN 50
98 // note: "51" is really rain total
99 #define RAIN_PER_DAY 51
100 #define RAIN_PER_HOUR 52
101 #define WIND_SPEED 60
102 #define WIND_DIRECTION 61
103 #define WIND_GUST 62
104 #define WIND_X 63
105 #define WIND_Y 64
106 #define MOISTURE 70
107 #define WATERLEVEL 71
108 #define WATERLEVEL_MIN 72
109 #define WATERLEVEL_MAX 73
110 #define BATTERY 110
111 #define TRANSMITTER 111
112 #define DURATION 120
113 #define SAMPLES 121
114
115 struct dbinfo
116 {
117 char user[30];
118 char pswrd[15];
119 char name[30];
120 } db;
121
122 char *progname;
123 char *query;
124 int *current = 0;
125
126 int opt;
127
128 MYSQL mysql; // Yeah, globals...
129 MYSQL_RES *result;
130 MYSQL_ROW row;
131
132 char last_timestamp[20];
133 char last_datetime[20];
134 char death_msg[120];
135
136 int debug_level;
137
138 char wxAPRShost[BUFLEN];
139 int wxAPRSport = PORT;
140 int outdoor_instr = OUTDOOR_SENSOR;
141
142 /******************************************************************
143 1/4/2003
144 Usage brief
145
146 *******************************************************************/
usage(int ret)147 void usage(int ret)
148 {
149 if (query)
150 {
151 printf("Content-type: text/plain\nStatus: 200\n\n");
152 }
153 printf("usage: %s [options] \n",progname);
154 printf("VERSION: %s\n",VERSION);
155 printf(" -h --help show this help and exit\n");
156 printf(" -v --verbose debugging info --> stderr\n");
157 printf(" -c --cport [port#] IP port for data output\n");
158 printf(" -s --sensor [sensor group#] from meteo, your OUTDOOR sensor set\n");
159 printf(" -u --user [database user] username for mysql - default meteo\n");
160 printf(" -p --password [db passwd] password for mysql - default none\n");
161 printf(" -b --database [database] database name - default meteo\n");
162 printf(" -n --nodaemon do not run as daemon\n");
163 printf(" -r --repeat keep running\n");
164 printf(" -i --interval [seconds] polling interval\n");
165 printf(" -m --metric data base is in metric units\n");
166 printf("options may be uniquely abbreviated; units are as defined in APRS\n");
167 printf("Specification 1.0.1 for positionless weather data (English/hPa).\n");
168 exit(ret);
169 }
170
171
172
173
174
175 /******************************************************************
176 1/2/2003
177 Make an APRS string out of WX data
178
179 *******************************************************************/
180
APRS_str(char * APRS_buf,char * datetime,double winddir,double windspeed,double windgust,double temp,double rain1hr,double rain24hr,double rainday,double humidity,double airpressure,unsigned int valid_data_flgs,int Metric_Data)181 int APRS_str(char *APRS_buf,
182 char *datetime,
183 double winddir,
184 double windspeed,
185 double windgust,
186 double temp,
187 double rain1hr,
188 double rain24hr,
189 double rainday,
190 double humidity,
191 double airpressure,
192 unsigned int valid_data_flgs,
193 int Metric_Data)
194 {
195
196 int intval;
197 char pbuf[10];
198
199 if (APRS_buf == NULL)
200 {
201 if (debug_level & 1)
202 {
203 fprintf(stderr,"err: Null string buffer for APRS string.\n");
204 }
205 return -1; // Ooo!! *****Nasty Bad Exit Point Here****
206
207 }
208 //timestamp first
209 sprintf(APRS_buf, "_%s",datetime);
210 //
211 if (valid_data_flgs & VALID_WINDDIR)
212 {
213 intval = (winddir + 0.5); // rounding to whole degrees
214 if (intval > 360)
215 {
216 if (debug_level & 1)
217 {
218 fprintf(stderr,"err: Wind direction > 360\n");
219 }
220 sprintf(APRS_buf, "c...");
221 }
222 else if (intval < 0)
223 {
224 if (debug_level & 1)
225 {
226 fprintf(stderr,"err: Wind direction negative\n");
227 }
228 sprintf(APRS_buf, "c...");
229
230 }
231 else
232 {
233 sprintf(pbuf, "c%0.3d", intval);
234 }
235 }
236 else
237 {
238 if (debug_level & 1)
239 {
240 fprintf(stderr,"info: Wind direction flagged as invalid\n");
241 }
242 sprintf(pbuf, "c...");
243
244 }
245 strcat(APRS_buf,pbuf);
246
247 if (valid_data_flgs & VALID_WINDSPD)
248 {
249 if (Metric_Data)
250 {
251 intval = (windspeed*MTPS2MPH + 0.5); // converting & rounding to whole MPH
252 }
253 else
254 {
255 intval = (windspeed + 0.5); // rounding to whole MPH
256 }
257 if (intval > 600) // Let's be reasonable here - center of a tornado??
258 {
259 if (debug_level & 1)
260 {
261 fprintf(stderr,"err: Wind speed > 600 MPH\n");
262 }
263 sprintf(pbuf, "s...");
264
265 }
266 else if (intval < 0)
267 {
268 if (debug_level & 1)
269 {
270 fprintf(stderr,"err: Wind speed negative\n");
271 }
272 sprintf(pbuf, "s...");
273
274 }
275 else
276 {
277 sprintf(pbuf, "s%0.3d", intval);
278 }
279 }
280 else
281 {
282 if (debug_level & 1)
283 {
284 fprintf(stderr,"info: Wind speed flagged as invalid\n");
285 }
286 sprintf(pbuf, "s...");
287
288 }
289 strcat(APRS_buf,pbuf);
290
291 if (valid_data_flgs & VALID_WINDGST)
292 {
293 if (Metric_Data)
294 {
295 intval = (windgust*MTPS2MPH + 0.5); // converting & rounding to whole MPH
296 }
297 else
298 {
299 intval = (windgust + 0.5); // rounding to whole MPH
300 }
301 if (intval > 600) // Let's be reasonable here - center of a tornado??
302 {
303 if (debug_level & 1)
304 {
305 fprintf(stderr,"err: Wind gust > 600 MPH\n");
306 }
307 sprintf(pbuf, "g...");
308
309 }
310 else if (intval < 0)
311 {
312 if (debug_level & 1)
313 {
314 fprintf(stderr,"err: Wind speed negative\n");
315 }
316 sprintf(pbuf, "g...");
317
318 }
319 else
320 {
321 sprintf(pbuf, "g%0.3d", intval);
322 }
323
324 }
325 else
326 {
327 if (debug_level & 1)
328 {
329 fprintf(stderr,"info: Wind speed flagged as invalid\n");
330 }
331 sprintf(pbuf, "g...");
332
333 }
334 strcat(APRS_buf,pbuf);
335
336 if (valid_data_flgs & VALID_TEMP)
337 {
338 if (Metric_Data)
339 {
340 intval = ((temp)*DEGC2DEGF + 0.5)+32; // converting & rounding to whole Deg F
341 }
342 else
343 {
344 intval = (temp + 0.5); // rounding to whole Deg F
345 }
346 if (intval > 200) // Let's be reasonable here - boiling?
347 {
348 if (debug_level & 1)
349 {
350 fprintf(stderr,"err: Temperature > 200 Deg F\n");
351 }
352 sprintf(pbuf, "t...");
353 }
354 else if (intval < -99)
355 {
356 if (debug_level & 1)
357 {
358 fprintf(stderr,"err: Temperature < -99 Deg F\n");
359 }
360 sprintf(pbuf, "t...");
361 }
362 else
363 {
364 if (intval < 0)
365 {
366 sprintf(pbuf,"t%0.2d",intval);
367 }
368 else
369 {
370 sprintf(pbuf, "t%0.3d", intval);
371 }
372 }
373 }
374 else
375 {
376 if (debug_level & 1)
377 {
378 fprintf(stderr,"info: Temperature flagged as invalid\n");
379 }
380 sprintf(pbuf, "t...");
381 }
382 strcat(APRS_buf,pbuf);
383
384 if (valid_data_flgs & VALID_RAIN)
385 {
386 if (Metric_Data)
387 {
388 intval = ((rain1hr)*MM2IN100TH + 0.5); // converting & rounding to whole 1/100 inch
389 }
390 else
391 {
392 intval = (rain1hr*100.0 + 0.5); // rounding to whole 1/100 inch
393 }
394 if (intval > 999) // 10 in/hr? Garden Hose -> rain gauge?
395 {
396 if (debug_level & 1)
397 {
398 fprintf(stderr,"err: Rainfall/Hr > 9.99 inch\n");
399 }
400 sprintf(pbuf, "\0\0\0\0");
401 }
402 else if (intval < -99)
403 {
404 if (debug_level & 1)
405 {
406 fprintf(stderr,"err: Rainfall/Hr negative\n");
407 }
408 sprintf(pbuf, "\0\0\0\0");
409 }
410 else
411 {
412 sprintf(pbuf, "r%0.3d", intval);
413 }
414 }
415 else
416 {
417
418 if (debug_level & 1)
419 {
420 fprintf(stderr,"info: Rainfall/Hr flagged as invalid\n");
421 }
422 sprintf(pbuf, "\0\0\0\0");
423
424 }
425 strcat(APRS_buf,pbuf);
426
427 if (valid_data_flgs & VALID_RAIN24H)
428 {
429 if (Metric_Data)
430 {
431 intval = ((rain24hr)*MM2IN100TH + 0.5); // converting & rounding to whole 1/100 inch
432 }
433 else
434 {
435 intval = (rain24hr*100.0 + 0.5); // rounding to whole 1/100 inch
436 }
437 if (intval > 999) // Can't handle greater than 9.99 inches of rain in 24 hours
438 {
439 if (debug_level & 1)
440 {
441 fprintf(stderr,"err: Rainfall/24Hr > 9.99 inch - reporting 9.99 inches\n");
442 }
443 sprintf(pbuf, "p999");
444
445 }
446 else if (intval < -99)
447 {
448 if (debug_level & 1)
449 {
450 fprintf(stderr,"err: Rainfall/Hr negative\n");
451 }
452 sprintf(pbuf, "\0\0\0\0");
453
454 }
455 else
456 {
457 sprintf(pbuf, "p%0.3d", intval);
458 }
459 }
460 else
461 {
462 if (debug_level & 1)
463 {
464 fprintf(stderr,"info: Rainfall/24Hr flagged as invalid\n");
465 }
466 sprintf(pbuf, "\0\0\0\0");
467 }
468 strcat(APRS_buf,pbuf);
469
470 if (valid_data_flgs & VALID_RAINDAY)
471 {
472 if (Metric_Data)
473 {
474 intval = ((rainday)*MM2IN100TH + 0.5); // converting & rounding to whole 1/100 inch
475 }
476 else
477 {
478 intval = (rainday*100.0 + 0.5); // rounding to whole 1/100 inch
479 }
480 if (intval > 999) // Can't handle greater than 9.99 inches of rain per day
481 {
482 if (debug_level & 1)
483 {
484 fprintf(stderr,"err: Rainfall/day > 9.99 inch - reporting 9.99 inches\n");
485 }
486 sprintf(pbuf, "P999");
487 }
488 else if (intval < -99)
489 {
490 if (debug_level & 1)
491 {
492 fprintf(stderr,"err: Rainfall/day negative\n");
493 }
494 sprintf(pbuf, "\0\0\0\0");
495 }
496 else
497 {
498 sprintf(pbuf, "P%0.3d", intval);
499 }
500 }
501 else
502 {
503 if (debug_level & 1)
504 {
505 fprintf(stderr,"info: Rainfall/day flagged as invalid\n");
506 }
507 sprintf(pbuf, "\0\0\0\0");
508 }
509 strcat(APRS_buf,pbuf);
510
511 if (valid_data_flgs & VALID_HUMIDITY)
512 {
513 intval = (humidity + 0.5); // rounding to whole percent
514 if (intval > 100) // Unlike the space shuttle engines, 100 % is max
515 {
516 if (debug_level & 1)
517 {
518 fprintf(stderr,"err: Humidity reported > 100%\n");
519 }
520 sprintf(pbuf, "\0\0\0\0");
521
522 }
523 else if (intval < 1)
524 {
525 if (debug_level & 1)
526 {
527 fprintf(stderr,"err: Humidity reported < 1%\n");
528 }
529 sprintf(pbuf, "\0\0\0\0");
530
531 }
532 else
533 {
534 if (intval == 100) // Report 100% as 'h00'
535 {
536 intval = 0;
537 }
538 sprintf(pbuf, "h%0.2d", intval);
539 }
540 }
541 else
542 {
543 if (debug_level & 1)
544 {
545 fprintf(stderr,"info: Humidity flagged as invalid\n");
546 }
547 sprintf(pbuf, "\0\0\0\0");
548
549 }
550 strcat(APRS_buf,pbuf);
551
552 if (valid_data_flgs & VALID_AIRPRESS)
553 {
554 if (Metric_Data)
555 {
556 intval = (airpressure*10.0 + 0.5); // rounding to whole tenth of a hPa
557 }
558 else
559 {
560 intval = (airpressure*INHG2HPA10TH + 0.5); // convering In-Hg to 1/10 hPa and rounding
561 }
562 if (intval > 20000) //two atmospheres - about 29 PSIA
563 {
564 if (debug_level & 1)
565 {
566 fprintf(stderr,"err: Air Pressure reported > 2 Atmospheres%\n");
567 }
568 sprintf(pbuf, "\0\0\0\0");
569 }
570 else if (intval < 0)
571 {
572 if (debug_level & 1)
573 {
574 fprintf(stderr,"err: Air Pressure reported negative%\n");
575 }
576 sprintf(pbuf, "\0\0\0\0");
577
578 }
579 else
580 {
581 sprintf(pbuf, "b%0.5d", intval);
582 }
583 }
584 else
585 {
586 if (debug_level & 1)
587 {
588 fprintf(stderr,"info: Air Pressure flagged as invalid\n");
589 }
590 sprintf(pbuf, "\0\0\0\0");
591
592 }
593 strcat(APRS_buf,pbuf);
594 strcat(APRS_buf,"xDvs\n"); // add X aprs and Davis WX station ID's and <lf>
595
596 if (debug_level & 1)
597 {
598 fprintf(stderr,"\ninfo: APRS Version of WX - %s\n\n",APRS_buf);
599 }
600 return strlen(APRS_buf);
601 }
602
603
604
605
606
607 /******************************************************************
608 1/2/2003
609 Get the latest set of Weather Data from the Data Base
610
611 *******************************************************************/
612
Get_Latest_WX(double * winddir,double * windspeed,double * windgust,double * temp,double * rain1hr,double * rain24hr,double * rainday,double * humidity,double * airpressure,unsigned int * valid_data_flgs)613 int Get_Latest_WX( double *winddir,
614 double *windspeed,
615 double *windgust,
616 double *temp,
617 double *rain1hr,
618 double *rain24hr,
619 double *rainday,
620 double *humidity,
621 double *airpressure,
622 unsigned int *valid_data_flgs)
623 {
624 long last_hour_timestamp;
625 long int last_24_timestamp;
626 long int last_day_timestamp;
627 char query_buffer[240];
628 int nrows, row_cnt, item_count;
629 int found_sensor;
630 long local_offset; // could be as big as 12 hrs times 3600 seconds...
631
632 // Find latest, see if it's new to us
633 // --new to us is a simple timestamp follower, so upon startup
634 // it will always read one set of data, assuming any exists
635
636 if (mysql_query(&mysql, "SELECT MAX(timekey) from sdata"))
637 {
638 sprintf(death_msg,"err: Latest timestamp query failed - exiting: %s\n", mysql_error(&mysql));
639 if (debug_level & 1)
640 {
641 fprintf(stderr,"%s",death_msg);
642 }
643 return -1;
644 }
645
646 if (!(result = mysql_store_result(&mysql)))
647 {
648 sprintf(death_msg,"err: Latest timestamp query failed - exiting: %s\n", mysql_error(&mysql));
649 if (debug_level & 1)
650 {
651 fprintf(stderr,"%s",death_msg);
652 }
653 return -1;
654 }
655
656 if (mysql_num_rows(result) != 1 )
657 {
658 sprintf(death_msg,"err: Latest timestamp query failed - exiting: number of results %d\n",
659 mysql_num_rows(result));
660 if (debug_level & 1)
661 {
662 fprintf(stderr,"%s",death_msg);
663 }
664 // release query buffer
665 mysql_free_result(result);
666 return -1;
667 }
668
669 row = mysql_fetch_row(result);
670
671 if ( row[0] == NULL )
672 {
673 sprintf(death_msg,"err: NULL result for timestamp query\n");
674 if (debug_level & 1)
675 {
676 fprintf(stderr,"%s",death_msg);
677 }
678 // release query buffer
679 mysql_free_result(result);
680 return -1;
681 }
682 // if no new data, exit with zero status
683
684 if (!strncmp(last_timestamp, row[0], 11))
685 {
686 if (debug_level & 1)
687 {
688 fprintf(stderr,"info: No new weather data recorded - exiting: no data\n");
689 }
690 // release query buffer
691 mysql_free_result(result);
692 return 0;
693 }
694 strcpy(last_timestamp, row[0]); // For next pass & following query
695
696 if ( debug_level & 1)
697 {
698 fprintf(stdout,"Timestamp: %s\n",last_timestamp);
699 }
700
701 // release query buffer
702 mysql_free_result(result);
703
704 sprintf(query_buffer,"SELECT value,sensorid,fieldid,from_unixtime(timekey,'%%m%%d%%H%%i%%S') FROM sdata WHERE timekey = %s", last_timestamp);
705
706 if (mysql_query(&mysql, query_buffer))
707 {
708 sprintf(death_msg,"err: Latest Weather Data query failed - exiting: %s\n\t --Query: %s\n",
709 mysql_error(&mysql), query_buffer);
710 if (debug_level & 1)
711 {
712 fprintf(stderr,"%s",death_msg);
713 }
714 return -1;
715 }
716
717 if (!(result = mysql_store_result(&mysql)))
718 {
719 sprintf(death_msg,"err: Latest Weather Data query failed - exiting: %s\n", mysql_error(&mysql));
720 if (debug_level & 1)
721 {
722 fprintf(stderr,"%s",death_msg);
723 }
724 return -1;
725 }
726 if ((nrows=mysql_num_rows(result)) < 1 )
727 {
728 sprintf(death_msg,"err: Latest Weather Data query failed - exiting: number of results %d\n",nrows);
729 if (debug_level & 1)
730 {
731 fprintf(stderr,"%s",death_msg);
732 }
733 // release query buffer
734 mysql_free_result(result);
735 return -1;
736 }
737 else
738 {
739 if (debug_level & 1)
740 {
741 fprintf(stderr,"info: Latest Weather Data query: number of types of readings %d\n",nrows);
742 }
743 }
744
745 *valid_data_flgs = 0;
746 item_count = 0;
747 for (row_cnt = 0; row_cnt < nrows; row_cnt++)
748 {
749 row = mysql_fetch_row(result);
750 strcpy(last_datetime,row[3]);
751
752 if (atoi(row[1]) == outdoor_instr) // sensors are really groups of data
753 {
754 found_sensor = atoi(row[1]);
755 if (debug_level & 1)
756 {
757 fprintf(stderr,"info: found an outdoor sensor: (%d) ",found_sensor);
758 }
759
760 switch (atoi(row[2])) // type of reading
761 {
762 case WIND_DIRECTION :
763 *winddir = strtod(row[0],NULL);
764 *valid_data_flgs |= VALID_WINDDIR;
765 item_count++;
766 if (debug_level & 1)
767 {
768 fprintf(stderr,"wind direction %f\n ",*winddir);
769 }
770 break;
771 case WIND_SPEED :
772 *windspeed = strtod(row[0],NULL);
773 *valid_data_flgs |= VALID_WINDSPD;
774 item_count++;
775 if (debug_level & 1)
776 {
777 fprintf(stderr,"wind speed %f\n ",*windspeed);
778 }
779 break;
780 case WIND_GUST :
781 *windgust = strtod(row[0],NULL);
782 *valid_data_flgs |= VALID_WINDGST;
783 item_count++;
784 if (debug_level & 1)
785 {
786 fprintf(stderr,"wind gust speed %f\n ",*windgust);
787 }
788 break;
789 case TEMPERATURE :
790 *temp = strtod(row[0],NULL);
791 *valid_data_flgs |= VALID_TEMP;
792 item_count++;
793 if (debug_level & 1)
794 {
795 fprintf(stderr,"temperature %f\n ",*temp);
796 }
797 break;
798 case HUMIDITY :
799 *humidity = strtod(row[0],NULL);
800 *valid_data_flgs |= VALID_HUMIDITY;
801 item_count++;
802 if (debug_level & 1)
803 {
804 fprintf(stderr,"humidity %f\n ",*humidity);
805 }
806 break;
807 case AIR_PRESSURE :
808 *airpressure = strtod(row[0],NULL);
809 *valid_data_flgs |= VALID_AIRPRESS;
810 item_count++;
811 if (debug_level & 1)
812 {
813 fprintf(stderr,"air pressure %f\n ",*airpressure);
814 }
815 break;
816 case RAIN_PER_DAY :
817 if (debug_level & 1)
818 {
819 fprintf(stderr,"rain-per-day total (not used), now calculated...\n ");
820 }
821 break;
822 case WIND_X :
823 if (debug_level & 1)
824 {
825 fprintf(stderr,"wind x... not used \n");
826 }
827 break;
828 case WIND_Y :
829 if (debug_level & 1)
830 {
831 fprintf(stderr,"wind y... not used \n");
832 }
833 break;
834 case DURATION :
835 if (debug_level & 1)
836 {
837 fprintf(stderr,"duration... not used \n");
838 }
839 break;
840 case SAMPLES :
841 if (debug_level & 1)
842 {
843 fprintf(stderr,"samples... not used \n");
844 }
845 break;
846 default :
847 if (debug_level & 1)
848 {
849 fprintf(stderr,"unknown field %s\n",row[2]);
850 }
851 break;
852 }
853 }
854 else // Must be indoor
855 {
856 if (debug_level & 1)
857 {
858 fprintf(stderr,"info: indoor sensor found\n");
859 }
860 switch (atoi(row[2])) // type of reading
861 {
862 case AIR_PRESSURE :
863 *airpressure = strtod(row[0],NULL);
864 *valid_data_flgs |= VALID_AIRPRESS;
865 item_count++;
866 if (debug_level & 1)
867 {
868 fprintf(stderr,"air pressure %f\n ",*airpressure);
869 }
870 break;
871 default :
872 if (debug_level & 1)
873 {
874 fprintf(stderr,"unused field %s\n",row[2]);
875 }
876 break;
877 }
878 }
879 }
880
881 if (debug_level & 1)
882 {
883 fprintf(stderr,"loop ends\n");
884 }
885 // release query buffer
886 mysql_free_result(result);
887
888 /* get rain figures */
889 /* */
890 /* hourly first */
891 last_hour_timestamp = atol(last_timestamp) - 3600;
892 sprintf(query_buffer,"SELECT round(sum(value),2) FROM sdata WHERE timekey > %ld and fieldid = %d", last_hour_timestamp,RAIN);
893
894 if (mysql_query(&mysql, query_buffer))
895 {
896 sprintf(death_msg,"err: rain 1 hour query failed - exiting: %s\n\t --Query: %s\n", mysql_error(&mysql), query_buffer);
897 if (debug_level & 1)
898 {
899 fprintf(stderr,"%s",death_msg);
900 }
901 return -1;
902 }
903 if (!(result = mysql_store_result(&mysql)))
904 {
905 sprintf(death_msg,"err: rain 1 hour store failed - exiting: %s\n", mysql_error(&mysql));
906 if (debug_level & 1)
907 {
908 fprintf(stderr,"%s",death_msg);
909 }
910 return -1;
911 }
912 if ((mysql_num_rows(result)) > 0)
913 {
914 row = mysql_fetch_row(result);
915 if (row[0] != NULL)
916 {
917 *rain1hr = strtod(row[0],NULL);
918 if (debug_level & 1)
919 {
920 fprintf(stderr,"rain last hour %f\n ",*rain1hr);
921 }
922 // *rain1hr = *rain1hr * 100; // AFTER metric conversion if needed
923 *valid_data_flgs |= VALID_RAIN;
924 item_count++;
925 }
926 else
927 {
928 *rain1hr = 0;
929 *valid_data_flgs |= VALID_RAIN; //None, but valid
930 if (debug_level & 1)
931 {
932 fprintf(stderr,"no rain recorded in last hour\n");
933 }
934 }
935 }
936 // release query buffer
937 mysql_free_result(result);
938
939 /* Last 24 hours */
940
941 last_24_timestamp = atol(last_timestamp) - 86400;
942 sprintf(query_buffer,"SELECT round(sum(value),2) FROM sdata WHERE timekey > %ld and fieldid = %d", last_24_timestamp,RAIN);
943
944 if (mysql_query(&mysql, query_buffer))
945 {
946 sprintf(death_msg,"err: rain 24 hour query failed - exiting: %s\n\t --Query: %s\n", mysql_error(&mysql), query_buffer);
947 if (debug_level & 1)
948 {
949 fprintf(stderr,"%s",death_msg);
950 }
951 return -1;
952 }
953 if (!(result = mysql_store_result(&mysql)))
954 {
955 sprintf(death_msg,"err: rain 24 hour store failed - exiting: %s\n", mysql_error(&mysql));
956 if (debug_level & 1)
957 {
958 fprintf(stderr,"%s",death_msg);
959 }
960 return -1;
961 }
962 if ((mysql_num_rows(result)) > 0)
963 {
964 row = mysql_fetch_row(result);
965 if (row[0] != NULL)
966 {
967 *rain24hr = strtod(row[0],NULL);
968 if (debug_level & 1)
969 {
970 fprintf(stderr,"rain last 24 hours %f\n ",*rain24hr);
971 }
972 //*rain24hr = *rain24hr * 100; // After metric conversion, if needed
973 item_count++;
974 *valid_data_flgs |= VALID_RAIN24H;
975 }
976 else
977 {
978 *rain24hr = 0;
979 *valid_data_flgs |= VALID_RAIN24H; // Zero is valid too
980 if (debug_level & 1)
981 {
982 fprintf(stderr,"no rain recorded in last 24 hours\n");
983 }
984 }
985 }
986 // release query buffer
987 mysql_free_result(result);
988 #define CALC_MIDNIGHT
989 #ifdef CALC_MIDNIGHT // Timestamps are seconds since midnight Jan 1 1970, so an integer divide and multiply by
990 // seconds in 24 hrs (86400) yields the latest midnight time stamp value.
991 last_day_timestamp = (atol(last_timestamp) / 86400)*86400;
992 if (debug_level & 1)
993 {
994 fprintf(stderr,"info: timestamp for prior midnight - %ld\n",last_day_timestamp);
995 }
996
997 // NOTE: Gcc warns that "found_sensor" could be uninitialized here.
998 sprintf(query_buffer,"SELECT offset FROM station WHERE id = (SELECT stationid from sensor WHERE id = %d)", found_sensor);
999 if (mysql_query(&mysql, query_buffer))
1000 {
1001 sprintf(death_msg,"err: station time offset query failed - exiting: %s\n\t --Query: %s\n", mysql_error(&mysql), query_buffer);
1002 if (debug_level & 1)
1003 {
1004 fprintf(stderr,"%s",death_msg);
1005 }
1006 return -1;
1007 }
1008 if (!(result = mysql_store_result(&mysql)))
1009 {
1010 sprintf(death_msg,"err: station time offset store failed - exiting: %s\n", mysql_error(&mysql));
1011 if (debug_level & 1)
1012 {
1013 fprintf(stderr,"%s",death_msg);
1014 }
1015 return -1;
1016 }
1017 if ((mysql_num_rows(result)) > 0)
1018 {
1019 row = mysql_fetch_row(result);
1020 if (row[0] != NULL)
1021 {
1022 local_offset = atol(row[0]);
1023 if (debug_level & 1)
1024 {
1025 fprintf(stderr,"info: station time offset: %ld\n", local_offset);
1026 }
1027 }
1028 }
1029 mysql_free_result(result);
1030 #else
1031
1032 /* since midnite */
1033
1034 /* we can get the timestamp for midnite from the avg table */
1035 last_day_timestamp = 0;
1036 sprintf(query_buffer,"SELECT max(timekey) FROM avg WHERE fieldid = %d and intval = 86400", last_hour_timestamp,RAIN);
1037 if (mysql_query(&mysql, query_buffer))
1038 {
1039 sprintf(death_msg,"err: midnite timekey query failed - exiting: %s\n\t --Query: %s\n", mysql_error(&mysql), query_buffer);
1040 if (debug_level & 1)
1041 {
1042 fprintf(stderr,"%s",death_msg);
1043 }
1044 return -1;
1045 }
1046 if (!(result = mysql_store_result(&mysql)))
1047 {
1048 sprintf(death_msg,"err: midnite timekey store failed - exiting: %s\n", mysql_error(&mysql));
1049 if (debug_level & 1)
1050 {
1051 fprintf(stderr,"%s",death_msg);
1052 }
1053 return -1;
1054 }
1055 if ((mysql_num_rows(result)) > 0)
1056 {
1057 row = mysql_fetch_row(result);
1058 if (row[0] != NULL)
1059 {
1060 last_day_timestamp = strtod(row[0],NULL);
1061 }
1062 }
1063 mysql_free_result(result);
1064 #endif
1065
1066 if (last_day_timestamp > 0)
1067 {
1068 #ifndef CALC_MIDNIGHT
1069 last_day_timestamp += 115200; // add 8 hours for offset - this should really be queried
1070 #else
1071 last_day_timestamp -= local_offset; // From the database
1072 #endif
1073 sprintf(query_buffer,"SELECT round(sum(value),2) FROM sdata WHERE timekey > %ld and fieldid = %d", last_day_timestamp,RAIN);
1074 if (mysql_query(&mysql, query_buffer))
1075 {
1076 sprintf(death_msg,"err: rain last day query failed - exiting: %s\n\t --Query: %s\n", mysql_error(&mysql), query_buffer);
1077 if (debug_level & 1)
1078 {
1079 fprintf(stderr,"%s",death_msg);
1080 }
1081 return -1;
1082 }
1083 if (!(result = mysql_store_result(&mysql)))
1084 {
1085 sprintf(death_msg,"err: rain last day store failed - exiting: %s\n", mysql_error(&mysql));
1086 if (debug_level & 1)
1087 {
1088 fprintf(stderr,"%s",death_msg);
1089 }
1090 return -1;
1091 }
1092 if ((mysql_num_rows(result)) > 0)
1093 {
1094 row = mysql_fetch_row(result);
1095 if (row[0] != NULL)
1096 {
1097 *rainday = strtod(row[0],NULL);
1098 if (debug_level & 1)
1099 {
1100 fprintf(stderr,"rain since midnite %f\n ",*rainday);
1101 }
1102 // *rainday = *rainday * 100; // AFTER metric conversion, if needed.
1103 item_count++;
1104 *valid_data_flgs |= VALID_RAINDAY;
1105 }
1106 else
1107 {
1108 *rainday = 0;
1109 *valid_data_flgs |= VALID_RAINDAY; // None is valid...
1110 if (debug_level & 1)
1111 {
1112 fprintf(stderr,"no rain recorded in last 24 hours\n");
1113 }
1114 }
1115 }
1116
1117 // release query buffer (where used)
1118 mysql_free_result(result);
1119
1120 }
1121 if (debug_level & 1)
1122 {
1123 fprintf(stderr,"info: success - Weather Data number of reading types %d\n",item_count);
1124 }
1125
1126 return item_count;
1127 }
1128
1129
1130
1131
1132
1133 /******************************************************************
1134 1/5/2003
1135 SIGPIPE signal handler
1136
1137 *******************************************************************/
pipe_handler(int sig)1138 void pipe_handler(int sig) /* */
1139 {
1140 signal(SIGPIPE, SIG_IGN);
1141 if (sig == SIGPIPE) // client went bye-bye
1142 {
1143 shutdown(*current, 2);
1144 close(*current);
1145 *current = -1;
1146 if (debug_level & 1)
1147 {
1148 fprintf(stderr, "info: %s - TCP client timed out", progname);
1149 }
1150 }
1151 }
1152
1153
1154
1155
1156
1157 /******************************************************************
1158 1/5/2003
1159 SIGTERM signal handler
1160
1161 *******************************************************************/
term_handler(int UNUSED (sig))1162 void term_handler( int UNUSED(sig) )
1163 {
1164 if (debug_level & 1)
1165 {
1166 fprintf(stderr, "info: %s - ordered to DIE, complying", progname);
1167 }
1168
1169 // release query buffer & close connection
1170 mysql_free_result(result);
1171
1172 mysql_close(&mysql);
1173
1174 exit( 0 );
1175 }
1176
1177
1178
1179
1180
1181 /******************************************************************
1182 1/2/2003
1183 Coordinating MAIN point
1184
1185 *******************************************************************/
main(int argc,char ** argv)1186 int main(int argc, char **argv)
1187 {
1188 const char *flags = "Hhvnmrc:u:p:d:s:i:";
1189 char WX_APRS[120];
1190 int data_len = 0 ;
1191 double winddir;
1192 double windspeed;
1193 double windgust;
1194 double temp;
1195 double rain1hr;
1196 double rain24hr;
1197 double rainday;
1198 double humidity;
1199 double airpressure;
1200 unsigned int valid_data_flgs;
1201 int Metric_Dat, dsts = 0;
1202 int pid, ss, fd[CONNECTIONS];
1203 socklen_t clen = sizeof(struct sockaddr_in);
1204 int *max = 0;
1205 int not_a_daemon = 0, repetitive = 0, tcp_wx_port = PORT;
1206 int i, index = 0;
1207 struct sockaddr_in server, client;
1208 struct in_addr bind_address;
1209 fd_set rfds;
1210 struct timeval tv;
1211 int poll_interval = POLL_INTERVAL;
1212 int dly_cnt = 1;
1213 FILE *pidfile;
1214 const char *pidfilename = "/var/run/db2APRS.pid";
1215
1216 struct option longopt[] =
1217 {
1218 {"help", 0, 0, 'h'},
1219 {"refresh", 0, 0, 'r'},
1220 {"verbose", 0, 0, 'v'},
1221 {"user", 1, 0, 'u'},
1222 {"password", 1, 0, 'p'},
1223 {"database", 1, 0, 'd'},
1224 {"cport", 1, 0, 'c'},
1225 {"sensor",1, 0, 's'},
1226 {"nodaemon", 0, 0, 'n'},
1227 {"interval",1, 0, 'i'},
1228 {"metric",0, 0, 'm'},
1229 {0, 0, 0, 0}
1230 };
1231
1232 debug_level = 0;
1233
1234 strcpy(db.user,"meteo"); // set default values for database access
1235 strcpy(db.name,"meteo");
1236 memset(db.pswrd,0,15);
1237
1238 mysql_init(&mysql);
1239
1240
1241 progname = strrchr(argv[0], '/');
1242 if (progname == NULL)
1243 {
1244 progname = argv[0];
1245 }
1246 else
1247 {
1248 progname++;
1249 }
1250
1251 while ((opt = getopt_long(argc, argv, flags, longopt, &index)) != EOF)
1252 {
1253 switch (opt) /* parse command-line or CGI options */
1254 {
1255 case 'r':
1256 repetitive = 1;
1257 break;
1258 case 'v':
1259 fprintf(stdout,"Verbose mode set:\n");
1260 debug_level = 1;
1261 break;
1262 case 'u': // mysql username
1263 strncpy(db.user,(char *)optarg,30);
1264 break;
1265 case 'p': // mysql password
1266 strncpy(db.pswrd,(char *)optarg,15);
1267 break;
1268 case 'd': // mysql database name
1269 strncpy(db.name,(char *)optarg,30);
1270 break;
1271 case 'n': /* do not fork and become a daemon */
1272 not_a_daemon = 1;
1273 break;
1274 case 'c': /* port to use */
1275 tcp_wx_port = strtol(optarg, NULL, 0);
1276 break;
1277 case 's': /* sensor number for outdoor data */
1278 outdoor_instr = atoi(optarg);
1279 break;
1280 case 'i': /* polling interval in seconds */
1281 poll_interval = atoi(optarg);
1282 break;
1283 case 'm': // Metric data from data base
1284 Metric_Dat = 1;
1285 break;
1286 case '?':
1287 case 'h':
1288 case 'H':
1289 usage(0);
1290 break;
1291 default :
1292 usage(1);
1293 }
1294 }
1295 if (debug_level & 1)
1296 {
1297 fprintf(stdout,"Starting...");
1298 if (repetitive)
1299 {
1300 fprintf(stdout, " forever ");
1301 }
1302 else
1303 {
1304 fprintf(stdout, " one pass only ");
1305 }
1306 fprintf(stdout," with database user=%s, password=%s, for database=%s\n",
1307 db.user, db.pswrd, db.name);
1308 if (not_a_daemon)
1309 {
1310 fprintf(stdout," as a program ");
1311 }
1312 else
1313 {
1314 fprintf(stdout," as a daemon ");
1315 }
1316 fprintf(stdout, " using TCP port %d\n",tcp_wx_port);
1317 fprintf(stdout, "an with an outdoor sensor group number of %d\n",outdoor_instr);
1318 }
1319
1320 if (!not_a_daemon) /* setup has worked; now become a daemon? */
1321 {
1322
1323 if ((pid = fork()) == -1)
1324 {
1325 syslog(LOG_ERR, "can't fork() to become daemon: %m");
1326 exit(20);
1327 }
1328 else if (pid)
1329 {
1330 pidfile = fopen(pidfilename, "w");
1331 fprintf(pidfile,"%d\n",pid);
1332 fclose(pidfile);
1333 exit (0);
1334 }
1335 syslog(LOG_ERR, "Started\n");
1336 setsid();
1337 for (i = 0; i < NOFILE; i++)
1338 {
1339 // NOTE: Gcc warns that "ss" could be uninitialized here.
1340 if ( i != ss)
1341 {
1342 close(i);
1343 }
1344 }
1345 }
1346
1347 // Data base connection
1348
1349
1350 if (!(mysql_real_connect(&mysql, "localhost", db.user, db.pswrd, db.name, 0, NULL, 0)))
1351 {
1352 if (debug_level & 1)
1353 {
1354 fprintf(stderr,"err: Data Base connect for user:%s to database:%s failed - exiting: \n\t%s\n",
1355 db.user, db.name, mysql_error(&mysql));
1356 }
1357 exit(9);
1358 }
1359
1360 server.sin_family = AF_INET;
1361 bind_address.s_addr = htonl(INADDR_ANY);
1362 server.sin_addr = bind_address;
1363 server.sin_port = htons(tcp_wx_port);
1364
1365 if ((ss = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
1366 {
1367 if (debug_level & 1)
1368 {
1369 fprintf(stderr, "err: %s - no socket", progname);
1370 }
1371 exit(10);
1372 }
1373 /* <dirkx@covalent.net> / April 2001 Minor change to allow quick
1374 * (re)start of deamon or client while there are pending
1375 * conncections during the quit. To avoid addresss/port in use
1376 * error. */
1377 i = 1;
1378 if (setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) == -1)
1379 {
1380 if (debug_level & 1)
1381 {
1382 fprintf(stderr, "err: %s - setsockopt", progname);
1383 }
1384 }
1385 if (bind(ss, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) == -1)
1386 {
1387 if (debug_level & 1)
1388 {
1389 fprintf(stderr, "err: %s - cannot bind to socket", progname);
1390 }
1391 exit(11);
1392 }
1393 if (listen(ss, CONNECTIONS) == -1)
1394 {
1395 if (debug_level & 1)
1396 {
1397 fprintf(stderr, "err: %s - listen", progname);
1398 }
1399 exit(12);
1400 }
1401
1402 if (debug_level & 1)
1403 {
1404 fprintf(stdout,"Sockets UP.\n");
1405 }
1406
1407 umask(0022);
1408 for (i = 0; i < CONNECTIONS; i++)
1409 {
1410 fd[i] = -1;
1411 }
1412 tv.tv_sec = 0;
1413 tv.tv_usec = 0;
1414
1415
1416 /* catch signals to close the database connection */
1417 signal( SIGTERM, term_handler );/* termination */
1418 #if defined(SIGPWR) /* SIGPWR is linux centric */
1419 signal( SIGPWR, term_handler ); /* power failure */
1420 #endif
1421 if (debug_level & 1)
1422 {
1423 fprintf(stdout,"Main Loop...\n");
1424 }
1425 dly_cnt = 2; // initial delay
1426 do
1427 {
1428 if (!(dly_cnt--))
1429 {
1430 dly_cnt = poll_interval; // Every 'dly_cnt' passes check for WX data update
1431 dsts = Get_Latest_WX(&winddir,&windspeed,&windgust,
1432 &temp,&rain1hr,&rain24hr,&rainday,&humidity,&airpressure, &valid_data_flgs);
1433 if ( dsts < 0 )
1434 {
1435 if (debug_level & 1)
1436 {
1437 fprintf(stderr, "err: Get_Latest returned %d\n",dsts);
1438 }
1439 syslog(LOG_ERR,death_msg);
1440 exit(dsts);
1441 }
1442 // If no new data, make no new string either...
1443 if (dsts)
1444 {
1445 data_len = APRS_str(WX_APRS, last_datetime, winddir,windspeed,windgust,
1446 temp, rain1hr, rain24hr, rainday, humidity, airpressure,
1447 valid_data_flgs,Metric_Dat);
1448
1449 if (!data_len)
1450 {
1451 if (debug_level & 1)
1452 {
1453 fprintf(stderr, "err: WX info formatting problem!");
1454 }
1455 syslog(LOG_ERR,"WX Data format error\n");
1456 exit(13);
1457 }
1458 }
1459 else
1460 {
1461 if (debug_level & 1)
1462 {
1463 fprintf(stderr,"Found no new data this pass...\n");
1464 }
1465 }
1466 }
1467 FD_ZERO(&rfds);
1468 FD_SET(ss, &rfds);
1469 if (select(ss + 1, &rfds, NULL, NULL, &tv) > 0)
1470 {
1471 for (current = fd; (*current > 0) && (current < fd + CONNECTIONS - 1); current++);
1472 if (current > max)
1473 {
1474 max = current;
1475 }
1476 if ((*current = accept(ss, (struct sockaddr *)&client, &clen)) != -1)
1477 {
1478 write(*current, WX_APRS, data_len);
1479 }
1480 }
1481 if (dly_cnt == poll_interval)
1482 {
1483 if (debug_level & 1)
1484 {
1485 fprintf(stdout,"Updating clients:");
1486 }
1487 for (current = fd; current <=max; current++)
1488 {
1489 if (*current > 0)
1490 {
1491 // active socket
1492 if (debug_level & 1)
1493 {
1494 fprintf(stdout," #");
1495 }
1496 signal(SIGPIPE, pipe_handler);
1497 write(*current, WX_APRS, data_len);
1498 }
1499 }
1500 if (debug_level & 1)
1501 {
1502 fprintf(stdout," done\n");
1503 }
1504 }
1505 sleep(1);
1506 }
1507 while (repetitive);
1508
1509 mysql_close(&mysql);
1510
1511 if (debug_level & 1)
1512 {
1513 fprintf(stdout,"Exiting normally.\n");
1514 }
1515 syslog(LOG_ERR,"Terminated normally\n");
1516 exit(0);
1517 }
1518
1519
1520