add emacs indentation variables to source files in line with current vim settings
[monky] / src / weather.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  *
3  * Conky, a system monitor, based on torsmo
4  *
5  * Please see COPYING for details
6  *
7  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
8  *      (see AUTHORS)
9  * All rights reserved.
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  * vim: ts=4 sw=4 noet ai cindent syntax=c
24  *
25  */
26
27 /*
28  * TODO: Add weather forecast info from weather.com
29  *
30  */
31
32 #include "conky.h"
33 #include "logging.h"
34 #include "weather.h"
35 #include "temphelper.h"
36 #include "ccurl_thread.h"
37 #include <time.h>
38 #include <ctype.h>
39 #ifdef MATH
40 #include <math.h>
41 #endif /* MATH */
42 #ifdef XOAP
43 #include <libxml/parser.h>
44 #include <libxml/xpath.h>
45
46 /* Xpath expressions for XOAP xml parsing */
47 #define NUM_XPATH_EXPRESSIONS 7
48 const char *xpath_expression[NUM_XPATH_EXPRESSIONS] = {
49         "/weather/cc/lsup", "/weather/cc/tmp", "/weather/cc/t",
50         "/weather/cc/bar/r", "/weather/cc/wind/s", "/weather/cc/wind/d",
51         "/weather/cc/hmid"
52 };
53 #endif /* XOAP */
54
55 /* Possible sky conditions */
56 #define NUM_CC_CODES 6
57 const char *CC_CODES[NUM_CC_CODES] = {
58         "SKC", "CLR", "FEW", "SCT", "BKN", "OVC"
59 };
60
61 /* Possible weather modifiers */
62 #define NUM_WM_CODES 9
63 const char *WM_CODES[NUM_WM_CODES] = {
64         "VC", "MI", "BC", "PR", "TS", "BL",
65         "SH", "DR", "FZ"
66 };
67
68 /* Possible weather conditions */
69 #define NUM_WC_CODES 17
70 const char *WC_CODES[NUM_WC_CODES] = {
71         "DZ", "RA", "GR", "GS", "SN", "SG",
72         "FG", "HZ", "FU", "BR", "DU", "SA",
73         "FC", "PO", "SQ", "SS", "DS"
74 };
75
76 static ccurl_location_t *locations_head = 0;
77
78 void weather_free_info(void)
79 {
80         ccurl_free_locations(&locations_head);
81 }
82
83 int rel_humidity(int dew_point, int air) {
84         const float a = 17.27f;
85         const float b = 237.7f;
86
87         float diff = a*(dew_point/(b+dew_point)-air/(b+air));
88 #ifdef MATH
89         return (int)(100.f*expf(diff));
90 #else
91         return (int)(16.666667163372f*(6.f+diff*(6.f+diff*(3.f+diff))));
92 #endif /* MATH */
93 }
94
95 #ifdef XOAP
96 static void parse_cc(PWEATHER *res, xmlXPathContextPtr xpathCtx)
97 {
98         int i;
99         char *content;
100         xmlXPathObjectPtr xpathObj;
101
102         for (i = 0; i < NUM_XPATH_EXPRESSIONS; i++) {
103           xpathObj = xmlXPathEvalExpression((xmlChar *)xpath_expression[i], xpathCtx);
104                 if ((xpathObj != NULL) && (xpathObj->nodesetval->nodeTab[0]->type == XML_ELEMENT_NODE)) {
105                   content = (char *)xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]);
106                   switch(i) {
107                        case 0:
108                             strncpy(res->lastupd, content, 31);
109                        break;
110                        case 1:
111                             res->temp = atoi(content);
112                        break;
113                        case 2:
114                             if(res->xoap_t[0] == '\0') {
115                                 strncpy(res->xoap_t, content, 31);
116                                 }
117                        break;
118                        case 3:
119                             res->bar = atoi(content);
120                        break;
121                        case 4:
122                             res->wind_s = atoi(content);
123                        break;
124                        case 5:
125                             if (isdigit((char)content[0])) {
126                                 res->wind_d = atoi(content);
127                             }
128                             break;
129                        case 6:
130                             res->hmid = atoi(content);
131                   }
132                   xmlFree(content);
133                 }
134                 xmlXPathFreeObject(xpathObj);
135         }
136         return;
137 }
138
139 static void parse_weather_xml(PWEATHER *res, const char *data)
140 {
141         xmlDocPtr doc;
142         xmlXPathContextPtr xpathCtx;
143
144         if (!(doc = xmlReadMemory(data, strlen(data), "", NULL, 0))) {
145                 ERR("weather: can't read xml data");
146                 return;
147         }
148
149         xpathCtx = xmlXPathNewContext(doc);
150         if(xpathCtx == NULL) {
151                 ERR("weather: unable to create new XPath context");
152                 xmlFreeDoc(doc);
153                 return;
154         }
155
156         parse_cc(res, xpathCtx);
157         xmlXPathFreeContext(xpathCtx);
158         xmlFreeDoc(doc);
159         return;
160 }
161 #endif /* XOAP */
162
163 /*
164  * Horrible hack to avoid using regexes
165  *
166  */
167
168 static inline void parse_token(PWEATHER *res, char *token) {
169
170         int i;
171         char s_tmp[64];
172
173         switch (strlen(token)) {
174
175                 //Check all tokens 2 chars long
176                 case 2:
177
178                         //Check if token is a weather condition
179                         for (i=0; i<2; i++) {
180                                 if (!isalpha(token[i])) break;
181                         }
182                         if (i==2) {
183                                 for(i=0; i<NUM_WC_CODES; i++) {
184                                         if (!strncmp(token, WC_CODES[i], 2)) {
185                                                 res->wc=i+1;
186                                                 break;
187                                         }
188                                 }
189                                 return;
190                         }
191
192                         //Check for CB
193                         if (!strcmp(token, "CB")) {
194                                 res->cc = 8;
195                                 return;
196                         }
197
198                         break;
199
200                         //Check all tokens 3 chars long
201                 case 3:
202
203                         //Check if token is a modified weather condition
204                         if ((token[0] == '+') || (token[0] == '-')) {
205                                 for (i=1; i<3; i++) {
206                                         if (!isalpha(token[i])) break;
207                                 }
208                                 if (i==3) {
209                                         for(i=0; i<NUM_WC_CODES; i++) {
210                                                 if (!strncmp(&token[1], WC_CODES[i], 2)) {
211                                                         res->wc=i+1;
212                                                         break;
213                                                 }
214                                         }
215                                         return;
216                                 }
217                         }
218
219                         //Check for NCD or NSC
220                         if ((!strcmp(token, "NCD")) || (!strcmp(token, "NSC"))) {
221                                 res->cc = 1;
222                                 return;
223                         }
224
225                         //Check for TCU
226                         if (!strcmp(token, "TCU")) {
227                                 res->cc = 7;
228                                 return;
229                         }
230
231                         break;
232
233                         //Check all tokens 4 chars long
234                 case 4:
235
236                         //Check if token is a modified weather condition
237                         for(i=0; i<NUM_WM_CODES; i++) {
238                                 if (!strncmp(token, WM_CODES[i], 2)) {
239                                         for(i=0; i<NUM_WC_CODES; i++) {
240                                                 if (!strncmp(&token[2], WC_CODES[i], 2)) {
241                                                         res->wc=i+1;
242                                                         return;
243                                                 }
244                                         }
245                                         break;
246                                 }
247                         }
248
249                         break;
250
251                         //Check all tokens 5 chars long
252                 case 5:
253
254                         //Check for CAVOK
255                         if (!strcmp(token, "CAVOK")) {
256                                 res->cc = 1;
257                                 return;
258                         }
259
260                         //Check if token is the temperature
261                         for (i=0; i<2; i++) {
262                                 if (!isdigit(token[i])) break;
263                         }
264                         if ((i==2) && (token[2] == '/')) {
265                                 for (i=3; i<5; i++) {
266                                         if (!isdigit(token[i])) break;
267                                 }
268                                 if (i==5) {
269                                         //First 2 digits gives the air temperature
270                                         res->temp=atoi(token);
271
272                                         //4th and 5th digits gives the dew point temperature
273                                         res->dew=atoi(&token[3]);
274
275                                         //Compute humidity
276                                         res->hmid = rel_humidity(res->dew, res->temp);
277
278                                         return;
279                                 }
280                         }
281
282                         //Check if token is the pressure
283                         if ((token[0] == 'Q') || (token[0] == 'A')) {
284                                 for (i=1; i<5; i++) {
285                                         if (!isdigit(token[i])) break;
286                                 }
287                                 if (i==5) {
288                                         if (token[0] == 'A') {
289                                                 //Convert inches of mercury to mbar
290                                                 res->bar = (int)(atoi(&token[1])*0.338637526f);
291                                                 return;
292                                         }
293
294                                         //Last 4 digits is pressure im mbar
295                                         res->bar = atoi(&token[1]);
296                                         return;
297                                 }
298                         }
299
300                         //Check if token is a modified weather condition
301                         if ((token[0] == '+') || (token[0] == '-')) {
302                                 for(i=0; i<NUM_WM_CODES; i++) {
303                                         if (!strncmp(&token[1], WM_CODES[i], 2)) {
304                                                 for(i=0; i<NUM_WC_CODES; i++) {
305                                                         if (!strncmp(&token[3], WC_CODES[i], 2)) {
306                                                                 res->wc=i+1;
307                                                                 return;
308                                                         }
309                                                 }
310                                                 break;
311                                         }
312                                 }
313                         }
314                         break;
315
316                         //Check all tokens 6 chars long
317                 case 6:
318
319                         //Check if token is the cloud cover
320                         for (i=0; i<3; i++) {
321                                 if (!isalpha(token[i])) break;
322                         }
323                         if (i==3) {
324                                 for (i=3; i<6; i++) {
325                                         if (!isdigit(token[i])) break;
326                                 }
327                                 if (i==6) {
328                                         //Check if first 3 digits gives the cloud cover condition
329                                         for(i=0; i<NUM_CC_CODES; i++) {
330                                                 if (!strncmp(token, CC_CODES[i], 3)) {
331                                                         res->cc=i+1;
332                                                         break;
333                                                 }
334                                         }
335                                         return;
336                                 }
337                         }
338
339                         //Check if token is positive temp and negative dew
340                         for (i=0; i<2; i++) {
341                                 if (!isdigit(token[i])) break;
342                         }
343                         if ((i==2) && (token[2] == '/')  && (token[3] == 'M')) {
344                                 for (i=4; i<6; i++) {
345                                         if (!isdigit(token[i])) break;
346                                 }
347                                 if (i==6) {
348                                         //1st and 2nd digits gives the temperature
349                                         res->temp = atoi(token);
350
351                                         //5th and 6th digits gives the dew point temperature
352                                         res->dew = -atoi(&token[4]);
353
354                                         //Compute humidity
355                                         res->hmid = rel_humidity(res->dew, res->temp);
356
357                                         return;
358                                 }
359                         }
360
361                         break;
362
363                         //Check all tokens 7 chars long
364                 case 7:
365
366                         //Check if token is the observation time
367                         for (i=0; i<6; i++) {
368                                 if (!isdigit(token[i])) break;
369                         }
370                         if ((i==6) && (token[6] == 'Z')) return;
371
372                         //Check if token is the wind speed/direction in knots
373                         for (i=0; i<5; i++) {
374                                 if (!isdigit(token[i])) break;
375                         }
376                         if ((i==5) && (token[5] == 'K') &&  (token[6] == 'T')) {
377
378                                 //First 3 digits are wind direction
379                                 strncpy(s_tmp, token, 3);
380                                 s_tmp[3]='\0';
381                                 res->wind_d=atoi(s_tmp);
382
383                                 //4th and 5th digit are wind speed in knots (convert to km/hr)
384                                 res->wind_s = (int)(atoi(&token[3])*1.852);
385
386                                 return;
387                         }
388
389                         //Check if token is negative temperature
390                         if ((token[0] == 'M') && (token[4] == 'M')) {
391                                 for (i=1; i<3; i++) {
392                                         if (!isdigit(token[i])) break;
393                                 }
394                                 if ((i==3) && (token[3] == '/')) {
395                                         for (i=5; i<7; i++) {
396                                                 if (!isdigit(token[i])) break;
397                                         }
398                                         if (i==7) {
399                                                 //2nd and 3rd digits gives the temperature
400                                                 res->temp = -atoi(&token[1]);
401
402                                                 //6th and 7th digits gives the dew point temperature
403                                                 res->dew = -atoi(&token[5]);
404
405                                                 //Compute humidity
406                                                 res->hmid = rel_humidity(res->dew, res->temp);
407
408                                                 return;
409                                         }
410                                 }
411                         }
412
413                         //Check if token is wind variability
414                         for (i=0; i<3; i++) {
415                                 if (!isdigit(token[i])) break;
416                         }
417                         if ((i==3) && (token[3] == 'V')) {
418                                 for (i=4; i<7; i++) {
419                                         if (!isdigit(token[i])) break;
420                                 }
421                                 if (i==7) return;
422                         }
423
424                         break;
425
426                         //Check all tokens 8 chars long
427                 case 8:
428
429                         //Check if token is the wind speed/direction in m/s
430                         for (i=0; i<5; i++) {
431                                 if (!isdigit(token[i])) break;
432                         }
433                         if ((i==5)&&(token[5] == 'M')&&(token[6] == 'P')&&(token[7] == 'S')) {
434
435                                 //First 3 digits are wind direction
436                                 strncpy(s_tmp, token, 3);
437                                 s_tmp[3]='\0';
438                                 res->wind_d=atoi(s_tmp);
439
440                                 //4th and 5th digit are wind speed in m/s (convert to km/hr)
441                                 res->wind_s = (int)(atoi(&token[3])*3.6);
442
443                                 return;
444                         }
445
446                 default:
447
448                         //printf("token : %s\n", token);
449                         break;
450         }
451 }
452
453 void parse_weather(void *result, const char *data)
454 {
455         PWEATHER *res = (PWEATHER*)result;
456         /* Reset results */
457         memset(res, 0, sizeof(PWEATHER));
458
459 #ifdef XOAP
460         //Check if it is an xml file
461         if ( strncmp(data, "<?xml ", 6) == 0 ) {
462                 parse_weather_xml(res, data);
463         } else
464 #endif /* XOAP */
465         {
466                 //We assume its a text file
467                 char s_tmp[256];
468                 const char delim[] = " ";
469
470                 //Divide time stamp and metar data
471                 if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
472
473                         //Process all tokens
474                         char *p_tok = NULL;
475                         char *p_save = NULL;
476
477                         if ((strtok_r(s_tmp, delim, &p_save)) != NULL) {
478
479                                 //Jump first token, must be icao
480                                 p_tok = strtok_r(NULL, delim, &p_save);
481
482                                 do {
483
484                                         parse_token(res, p_tok);
485                                         p_tok = strtok_r(NULL, delim, &p_save);
486
487                                 } while (p_tok != NULL);
488                         }
489                         return;
490                 }
491                 else {
492                         return;
493                 }
494         }
495 }
496
497 void weather_process_info(char *p, int p_max_size, char *uri, char *data_type, int interval)
498 {
499         static const char *wc[] = {
500                 "", "drizzle", "rain", "hail", "soft hail",
501                 "snow", "snow grains", "fog", "haze", "smoke",
502                 "mist", "dust", "sand", "funnel cloud tornado",
503                 "dust/sand", "squall", "sand storm", "dust storm"
504         };
505         PWEATHER *data;
506
507         ccurl_location_t *curloc = ccurl_find_location(&locations_head, uri);
508         if (!curloc->p_timed_thread) {
509                 curloc->result = malloc(sizeof(PWEATHER));
510                 memset(curloc->result, 0, sizeof(PWEATHER));
511                 curloc->process_function = &parse_weather;
512                 ccurl_init_thread(curloc, interval);
513                 if (!curloc->p_timed_thread) {
514                         ERR("error setting up weather thread");
515                 }
516         }
517
518         timed_thread_lock(curloc->p_timed_thread);
519         data = (PWEATHER*)curloc->result;
520         if (strcmp(data_type, "last_update") == EQUAL) {
521                 strncpy(p, data->lastupd, p_max_size);
522         } else if (strcmp(data_type, "temperature") == EQUAL) {
523                 temp_print(p, p_max_size, data->temp, TEMP_CELSIUS);
524         } else if (strcmp(data_type, "cloud_cover") == EQUAL) {
525 #ifdef XOAP
526                 if (data->xoap_t[0] != '\0') {
527                         strncpy(p, data->xoap_t, p_max_size);
528                 } else
529 #endif /* XOAP */
530                         if (data->cc == 0) {
531                                 strncpy(p, "", p_max_size);
532                         } else if (data->cc < 3) {
533                                 strncpy(p, "clear", p_max_size);
534                         } else if (data->cc < 5) {
535                                 strncpy(p, "partly cloudy", p_max_size);
536                         } else if (data->cc == 5) {
537                                 strncpy(p, "cloudy", p_max_size);
538                         } else if (data->cc == 6) {
539                                 strncpy(p, "overcast", p_max_size);
540                         } else if (data->cc == 7) {
541                                 strncpy(p, "towering cumulus", p_max_size);
542                         } else  {
543                                 strncpy(p, "cumulonimbus", p_max_size);
544                         }
545         } else if (strcmp(data_type, "pressure") == EQUAL) {
546                 snprintf(p, p_max_size, "%d", data->bar);
547         } else if (strcmp(data_type, "wind_speed") == EQUAL) {
548                 snprintf(p, p_max_size, "%d", data->wind_s);
549         } else if (strcmp(data_type, "wind_dir") == EQUAL) {
550                 if ((data->wind_d >= 349) || (data->wind_d < 12)) {
551                         strncpy(p, "N", p_max_size);
552                 } else if (data->wind_d < 33) {
553                         strncpy(p, "NNE", p_max_size);
554                 } else if (data->wind_d < 57) {
555                         strncpy(p, "NE", p_max_size);
556                 } else if (data->wind_d < 79) {
557                         strncpy(p, "ENE", p_max_size);
558                 } else if (data->wind_d < 102) {
559                         strncpy(p, "E", p_max_size);
560                 } else if (data->wind_d < 124) {
561                         strncpy(p, "ESE", p_max_size);
562                 } else if (data->wind_d < 147) {
563                         strncpy(p, "SE", p_max_size);
564                 } else if (data->wind_d < 169) {
565                         strncpy(p, "SSE", p_max_size);
566                 } else if (data->wind_d < 192) {
567                         strncpy(p, "S", p_max_size);
568                 } else if (data->wind_d < 214) {
569                         strncpy(p, "SSW", p_max_size);
570                 } else if (data->wind_d < 237) {
571                         strncpy(p, "SW", p_max_size);
572                 } else if (data->wind_d < 259) {
573                         strncpy(p, "WSW", p_max_size);
574                 } else if (data->wind_d < 282) {
575                         strncpy(p, "W", p_max_size);
576                 } else if (data->wind_d < 304) {
577                         strncpy(p, "WNW", p_max_size);
578                 } else if (data->wind_d < 327) {
579                         strncpy(p, "NW", p_max_size);
580                 } else if (data->wind_d < 349) {
581                         strncpy(p, "NNW", p_max_size);
582                 };
583         } else if (strcmp(data_type, "wind_dir_DEG") == EQUAL) {
584                 snprintf(p, p_max_size, "%d", data->wind_d);
585
586         } else if (strcmp(data_type, "humidity") == EQUAL) {
587                 snprintf(p, p_max_size, "%d", data->hmid);
588         } else if (strcmp(data_type, "weather") == EQUAL) {
589                 strncpy(p, wc[data->wc], p_max_size);
590         }
591
592         timed_thread_unlock(curloc->p_timed_thread);
593 }
594
595 #ifdef XOAP
596
597 /* xoap suffix for weather from weather.com */
598 static char *xoap = NULL;
599
600 /*
601  * TODO: make the xoap keys file readable from the config file
602  *       make the keys directly readable from the config file
603  *       make the xoap keys file giveable as a command line option
604  */
605 void load_xoap_keys(void)
606 {
607         FILE *fp;
608         char *par = (char *) malloc(11 * sizeof(char));
609         char *key = (char *) malloc(17 * sizeof(char));
610
611         xoap = (char *) malloc(64 * sizeof(char));
612         to_real_path(xoap, XOAP_FILE);
613         fp = fopen(xoap, "r");
614         if (fp != NULL) {
615                 if (fscanf(fp, "%10s %16s", par, key) == 2) {
616                         strcpy(xoap, "?cc=*&link=xoap&prod=xoap&par=");
617                         strcat(xoap, par);
618                         strcat(xoap, "&key=");
619                         strcat(xoap, key);
620                         strcat(xoap, "&unit=m");
621                 } else {
622                         free(xoap);
623                         xoap = NULL;
624                 }
625                 fclose(fp);
626         } else {
627                 free(xoap);
628                 xoap = NULL;
629         }
630         free(par);
631         free(key);
632 }
633 #endif /* XOAP */
634
635 int process_weather_uri(char *uri, char *locID)
636 {
637         /* locID MUST BE upper-case */
638         char *tmp_p = locID;
639         while (*tmp_p) {
640                 *tmp_p = toupper(*tmp_p);
641                 tmp_p++;
642         }
643
644         /* Construct complete uri */
645 #ifdef XOAP
646         if (strstr(uri, "xoap.weather.com")) {
647                 if (xoap != NULL) {
648                         strcat(uri, locID);
649                         strcat(uri, xoap);
650                 } else {
651                         free(uri);
652                         uri = NULL;
653                 }
654         } else 
655 #endif /* XOAP */
656         if (strstr(uri, "weather.noaa.gov")) {
657                 strcat(uri, locID);
658                 strcat(uri, ".TXT");
659         } else  if (!strstr(uri, "localhost") && !strstr(uri, "127.0.0.1")) {
660                 return -1;
661         }
662         return 0;
663 }
664