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