weather: add icon data_type for xoap
[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 8
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", "/weather/cc/icon"
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                                 break;
132                        case 7:
133                             strncpy(res->icon, content, 2);
134                   }
135                   xmlFree(content);
136                 }
137                 xmlXPathFreeObject(xpathObj);
138         }
139         return;
140 }
141
142 static void parse_weather_xml(PWEATHER *res, const char *data)
143 {
144         xmlDocPtr doc;
145         xmlXPathContextPtr xpathCtx;
146
147         if (!(doc = xmlReadMemory(data, strlen(data), "", NULL, 0))) {
148                 ERR("weather: can't read xml data");
149                 return;
150         }
151
152         xpathCtx = xmlXPathNewContext(doc);
153         if(xpathCtx == NULL) {
154                 ERR("weather: unable to create new XPath context");
155                 xmlFreeDoc(doc);
156                 return;
157         }
158
159         parse_cc(res, xpathCtx);
160         xmlXPathFreeContext(xpathCtx);
161         xmlFreeDoc(doc);
162         return;
163 }
164 #endif /* XOAP */
165
166 /*
167  * Horrible hack to avoid using regexes
168  *
169  */
170
171 static inline void parse_token(PWEATHER *res, char *token) {
172
173         int i;
174         char s_tmp[64];
175
176         switch (strlen(token)) {
177
178                 //Check all tokens 2 chars long
179                 case 2:
180
181                         //Check if token is a weather condition
182                         for (i=0; i<2; i++) {
183                                 if (!isalpha(token[i])) break;
184                         }
185                         if (i==2) {
186                                 for(i=0; i<NUM_WC_CODES; i++) {
187                                         if (!strncmp(token, WC_CODES[i], 2)) {
188                                                 res->wc=i+1;
189                                                 break;
190                                         }
191                                 }
192                                 return;
193                         }
194
195                         //Check for CB
196                         if (!strcmp(token, "CB")) {
197                                 res->cc = 8;
198                                 return;
199                         }
200
201                         break;
202
203                         //Check all tokens 3 chars long
204                 case 3:
205
206                         //Check if token is a modified weather condition
207                         if ((token[0] == '+') || (token[0] == '-')) {
208                                 for (i=1; i<3; i++) {
209                                         if (!isalpha(token[i])) break;
210                                 }
211                                 if (i==3) {
212                                         for(i=0; i<NUM_WC_CODES; i++) {
213                                                 if (!strncmp(&token[1], WC_CODES[i], 2)) {
214                                                         res->wc=i+1;
215                                                         break;
216                                                 }
217                                         }
218                                         return;
219                                 }
220                         }
221
222                         //Check for NCD or NSC
223                         if ((!strcmp(token, "NCD")) || (!strcmp(token, "NSC"))) {
224                                 res->cc = 1;
225                                 return;
226                         }
227
228                         //Check for TCU
229                         if (!strcmp(token, "TCU")) {
230                                 res->cc = 7;
231                                 return;
232                         }
233
234                         break;
235
236                         //Check all tokens 4 chars long
237                 case 4:
238
239                         //Check if token is a modified weather condition
240                         for(i=0; i<NUM_WM_CODES; i++) {
241                                 if (!strncmp(token, WM_CODES[i], 2)) {
242                                         for(i=0; i<NUM_WC_CODES; i++) {
243                                                 if (!strncmp(&token[2], WC_CODES[i], 2)) {
244                                                         res->wc=i+1;
245                                                         return;
246                                                 }
247                                         }
248                                         break;
249                                 }
250                         }
251
252                         break;
253
254                         //Check all tokens 5 chars long
255                 case 5:
256
257                         //Check for CAVOK
258                         if (!strcmp(token, "CAVOK")) {
259                                 res->cc = 1;
260                                 return;
261                         }
262
263                         //Check if token is the temperature
264                         for (i=0; i<2; i++) {
265                                 if (!isdigit(token[i])) break;
266                         }
267                         if ((i==2) && (token[2] == '/')) {
268                                 for (i=3; i<5; i++) {
269                                         if (!isdigit(token[i])) break;
270                                 }
271                                 if (i==5) {
272                                         //First 2 digits gives the air temperature
273                                         res->temp=atoi(token);
274
275                                         //4th and 5th digits gives the dew point temperature
276                                         res->dew=atoi(&token[3]);
277
278                                         //Compute humidity
279                                         res->hmid = rel_humidity(res->dew, res->temp);
280
281                                         return;
282                                 }
283                         }
284
285                         //Check if token is the pressure
286                         if ((token[0] == 'Q') || (token[0] == 'A')) {
287                                 for (i=1; i<5; i++) {
288                                         if (!isdigit(token[i])) break;
289                                 }
290                                 if (i==5) {
291                                         if (token[0] == 'A') {
292                                                 //Convert inches of mercury to mbar
293                                                 res->bar = (int)(atoi(&token[1])*0.338637526f);
294                                                 return;
295                                         }
296
297                                         //Last 4 digits is pressure im mbar
298                                         res->bar = atoi(&token[1]);
299                                         return;
300                                 }
301                         }
302
303                         //Check if token is a modified weather condition
304                         if ((token[0] == '+') || (token[0] == '-')) {
305                                 for(i=0; i<NUM_WM_CODES; i++) {
306                                         if (!strncmp(&token[1], WM_CODES[i], 2)) {
307                                                 for(i=0; i<NUM_WC_CODES; i++) {
308                                                         if (!strncmp(&token[3], WC_CODES[i], 2)) {
309                                                                 res->wc=i+1;
310                                                                 return;
311                                                         }
312                                                 }
313                                                 break;
314                                         }
315                                 }
316                         }
317                         break;
318
319                         //Check all tokens 6 chars long
320                 case 6:
321
322                         //Check if token is the cloud cover
323                         for (i=0; i<3; i++) {
324                                 if (!isalpha(token[i])) break;
325                         }
326                         if (i==3) {
327                                 for (i=3; i<6; i++) {
328                                         if (!isdigit(token[i])) break;
329                                 }
330                                 if (i==6) {
331                                         //Check if first 3 digits gives the cloud cover condition
332                                         for(i=0; i<NUM_CC_CODES; i++) {
333                                                 if (!strncmp(token, CC_CODES[i], 3)) {
334                                                         res->cc=i+1;
335                                                         break;
336                                                 }
337                                         }
338                                         return;
339                                 }
340                         }
341
342                         //Check if token is positive temp and negative dew
343                         for (i=0; i<2; i++) {
344                                 if (!isdigit(token[i])) break;
345                         }
346                         if ((i==2) && (token[2] == '/')  && (token[3] == 'M')) {
347                                 for (i=4; i<6; i++) {
348                                         if (!isdigit(token[i])) break;
349                                 }
350                                 if (i==6) {
351                                         //1st and 2nd digits gives the temperature
352                                         res->temp = atoi(token);
353
354                                         //5th and 6th digits gives the dew point temperature
355                                         res->dew = -atoi(&token[4]);
356
357                                         //Compute humidity
358                                         res->hmid = rel_humidity(res->dew, res->temp);
359
360                                         return;
361                                 }
362                         }
363
364                         break;
365
366                         //Check all tokens 7 chars long
367                 case 7:
368
369                         //Check if token is the observation time
370                         for (i=0; i<6; i++) {
371                                 if (!isdigit(token[i])) break;
372                         }
373                         if ((i==6) && (token[6] == 'Z')) return;
374
375                         //Check if token is the wind speed/direction in knots
376                         for (i=0; i<5; i++) {
377                                 if (!isdigit(token[i])) break;
378                         }
379                         if ((i==5) && (token[5] == 'K') &&  (token[6] == 'T')) {
380
381                                 //First 3 digits are wind direction
382                                 strncpy(s_tmp, token, 3);
383                                 s_tmp[3]='\0';
384                                 res->wind_d=atoi(s_tmp);
385
386                                 //4th and 5th digit are wind speed in knots (convert to km/hr)
387                                 res->wind_s = (int)(atoi(&token[3])*1.852);
388
389                                 return;
390                         }
391
392                         //Check if token is negative temperature
393                         if ((token[0] == 'M') && (token[4] == 'M')) {
394                                 for (i=1; i<3; i++) {
395                                         if (!isdigit(token[i])) break;
396                                 }
397                                 if ((i==3) && (token[3] == '/')) {
398                                         for (i=5; i<7; i++) {
399                                                 if (!isdigit(token[i])) break;
400                                         }
401                                         if (i==7) {
402                                                 //2nd and 3rd digits gives the temperature
403                                                 res->temp = -atoi(&token[1]);
404
405                                                 //6th and 7th digits gives the dew point temperature
406                                                 res->dew = -atoi(&token[5]);
407
408                                                 //Compute humidity
409                                                 res->hmid = rel_humidity(res->dew, res->temp);
410
411                                                 return;
412                                         }
413                                 }
414                         }
415
416                         //Check if token is wind variability
417                         for (i=0; i<3; i++) {
418                                 if (!isdigit(token[i])) break;
419                         }
420                         if ((i==3) && (token[3] == 'V')) {
421                                 for (i=4; i<7; i++) {
422                                         if (!isdigit(token[i])) break;
423                                 }
424                                 if (i==7) return;
425                         }
426
427                         break;
428
429                         //Check all tokens 8 chars long
430                 case 8:
431
432                         //Check if token is the wind speed/direction in m/s
433                         for (i=0; i<5; i++) {
434                                 if (!isdigit(token[i])) break;
435                         }
436                         if ((i==5)&&(token[5] == 'M')&&(token[6] == 'P')&&(token[7] == 'S')) {
437
438                                 //First 3 digits are wind direction
439                                 strncpy(s_tmp, token, 3);
440                                 s_tmp[3]='\0';
441                                 res->wind_d=atoi(s_tmp);
442
443                                 //4th and 5th digit are wind speed in m/s (convert to km/hr)
444                                 res->wind_s = (int)(atoi(&token[3])*3.6);
445
446                                 return;
447                         }
448
449                 default:
450
451                         //printf("token : %s\n", token);
452                         break;
453         }
454 }
455
456 void parse_weather(void *result, const char *data)
457 {
458         PWEATHER *res = (PWEATHER*)result;
459         /* Reset results */
460         memset(res, 0, sizeof(PWEATHER));
461
462 #ifdef XOAP
463         //Check if it is an xml file
464         if ( strncmp(data, "<?xml ", 6) == 0 ) {
465                 parse_weather_xml(res, data);
466         } else
467 #endif /* XOAP */
468         {
469                 //We assume its a text file
470                 char s_tmp[256];
471                 const char delim[] = " ";
472
473                 //Divide time stamp and metar data
474                 if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
475
476                         //Process all tokens
477                         char *p_tok = NULL;
478                         char *p_save = NULL;
479
480                         if ((strtok_r(s_tmp, delim, &p_save)) != NULL) {
481
482                                 //Jump first token, must be icao
483                                 p_tok = strtok_r(NULL, delim, &p_save);
484
485                                 do {
486
487                                         parse_token(res, p_tok);
488                                         p_tok = strtok_r(NULL, delim, &p_save);
489
490                                 } while (p_tok != NULL);
491                         }
492                         return;
493                 }
494                 else {
495                         return;
496                 }
497         }
498 }
499
500 void weather_process_info(char *p, int p_max_size, char *uri, char *data_type, int interval)
501 {
502         static const char *wc[] = {
503                 "", "drizzle", "rain", "hail", "soft hail",
504                 "snow", "snow grains", "fog", "haze", "smoke",
505                 "mist", "dust", "sand", "funnel cloud tornado",
506                 "dust/sand", "squall", "sand storm", "dust storm"
507         };
508         PWEATHER *data;
509
510         ccurl_location_t *curloc = ccurl_find_location(&locations_head, uri);
511         if (!curloc->p_timed_thread) {
512                 curloc->result = malloc(sizeof(PWEATHER));
513                 memset(curloc->result, 0, sizeof(PWEATHER));
514                 curloc->process_function = &parse_weather;
515                 ccurl_init_thread(curloc, interval);
516                 if (!curloc->p_timed_thread) {
517                         ERR("error setting up weather thread");
518                 }
519         }
520
521         timed_thread_lock(curloc->p_timed_thread);
522         data = (PWEATHER*)curloc->result;
523         if (strcmp(data_type, "last_update") == EQUAL) {
524                 strncpy(p, data->lastupd, p_max_size);
525         } else if (strcmp(data_type, "temperature") == EQUAL) {
526                 temp_print(p, p_max_size, data->temp, TEMP_CELSIUS);
527         } else if (strcmp(data_type, "cloud_cover") == EQUAL) {
528 #ifdef XOAP
529                 if (data->xoap_t[0] != '\0') {
530                         strncpy(p, data->xoap_t, p_max_size);
531                 } else
532 #endif /* XOAP */
533                         if (data->cc == 0) {
534                                 strncpy(p, "", p_max_size);
535                         } else if (data->cc < 3) {
536                                 strncpy(p, "clear", p_max_size);
537                         } else if (data->cc < 5) {
538                                 strncpy(p, "partly cloudy", p_max_size);
539                         } else if (data->cc == 5) {
540                                 strncpy(p, "cloudy", p_max_size);
541                         } else if (data->cc == 6) {
542                                 strncpy(p, "overcast", p_max_size);
543                         } else if (data->cc == 7) {
544                                 strncpy(p, "towering cumulus", p_max_size);
545                         } else  {
546                                 strncpy(p, "cumulonimbus", p_max_size);
547                         }
548 #ifdef XOAP
549         } else if (strcmp(data_type, "icon") == EQUAL) {
550                 strncpy(p, data->icon, p_max_size);
551 #endif /* XOAP */
552         } else if (strcmp(data_type, "pressure") == EQUAL) {
553                 snprintf(p, p_max_size, "%d", data->bar);
554         } else if (strcmp(data_type, "wind_speed") == EQUAL) {
555                 snprintf(p, p_max_size, "%d", data->wind_s);
556         } else if (strcmp(data_type, "wind_dir") == EQUAL) {
557                 if ((data->wind_d >= 349) || (data->wind_d < 12)) {
558                         strncpy(p, "N", p_max_size);
559                 } else if (data->wind_d < 33) {
560                         strncpy(p, "NNE", p_max_size);
561                 } else if (data->wind_d < 57) {
562                         strncpy(p, "NE", p_max_size);
563                 } else if (data->wind_d < 79) {
564                         strncpy(p, "ENE", p_max_size);
565                 } else if (data->wind_d < 102) {
566                         strncpy(p, "E", p_max_size);
567                 } else if (data->wind_d < 124) {
568                         strncpy(p, "ESE", p_max_size);
569                 } else if (data->wind_d < 147) {
570                         strncpy(p, "SE", p_max_size);
571                 } else if (data->wind_d < 169) {
572                         strncpy(p, "SSE", p_max_size);
573                 } else if (data->wind_d < 192) {
574                         strncpy(p, "S", p_max_size);
575                 } else if (data->wind_d < 214) {
576                         strncpy(p, "SSW", p_max_size);
577                 } else if (data->wind_d < 237) {
578                         strncpy(p, "SW", p_max_size);
579                 } else if (data->wind_d < 259) {
580                         strncpy(p, "WSW", p_max_size);
581                 } else if (data->wind_d < 282) {
582                         strncpy(p, "W", p_max_size);
583                 } else if (data->wind_d < 304) {
584                         strncpy(p, "WNW", p_max_size);
585                 } else if (data->wind_d < 327) {
586                         strncpy(p, "NW", p_max_size);
587                 } else if (data->wind_d < 349) {
588                         strncpy(p, "NNW", p_max_size);
589                 };
590         } else if (strcmp(data_type, "wind_dir_DEG") == EQUAL) {
591                 snprintf(p, p_max_size, "%d", data->wind_d);
592
593         } else if (strcmp(data_type, "humidity") == EQUAL) {
594                 snprintf(p, p_max_size, "%d", data->hmid);
595         } else if (strcmp(data_type, "weather") == EQUAL) {
596                 strncpy(p, wc[data->wc], p_max_size);
597         }
598
599         timed_thread_unlock(curloc->p_timed_thread);
600 }
601
602 #ifdef XOAP
603
604 /* xoap suffix for weather from weather.com */
605 static char *xoap = NULL;
606
607 /*
608  * TODO: make the xoap keys file readable from the config file
609  *       make the keys directly readable from the config file
610  *       make the xoap keys file giveable as a command line option
611  */
612 void load_xoap_keys(void)
613 {
614         FILE *fp;
615         char *par = (char *) malloc(11 * sizeof(char));
616         char *key = (char *) malloc(17 * sizeof(char));
617
618         xoap = (char *) malloc(64 * sizeof(char));
619         to_real_path(xoap, XOAP_FILE);
620         fp = fopen(xoap, "r");
621         if (fp != NULL) {
622                 if (fscanf(fp, "%10s %16s", par, key) == 2) {
623                         strcpy(xoap, "?cc=*&link=xoap&prod=xoap&par=");
624                         strcat(xoap, par);
625                         strcat(xoap, "&key=");
626                         strcat(xoap, key);
627                         strcat(xoap, "&unit=m");
628                 } else {
629                         free(xoap);
630                         xoap = NULL;
631                 }
632                 fclose(fp);
633         } else {
634                 free(xoap);
635                 xoap = NULL;
636         }
637         free(par);
638         free(key);
639 }
640 #endif /* XOAP */
641
642 int process_weather_uri(char *uri, char *locID)
643 {
644         /* locID MUST BE upper-case */
645         char *tmp_p = locID;
646         while (*tmp_p) {
647                 *tmp_p = toupper(*tmp_p);
648                 tmp_p++;
649         }
650
651         /* Construct complete uri */
652 #ifdef XOAP
653         if (strstr(uri, "xoap.weather.com")) {
654                 if (xoap != NULL) {
655                         strcat(uri, locID);
656                         strcat(uri, xoap);
657                 } else {
658                         free(uri);
659                         uri = NULL;
660                 }
661         } else 
662 #endif /* XOAP */
663         if (strstr(uri, "weather.noaa.gov")) {
664                 strcat(uri, locID);
665                 strcat(uri, ".TXT");
666         } else  if (!strstr(uri, "localhost") && !strstr(uri, "127.0.0.1")) {
667                 return -1;
668         }
669         return 0;
670 }
671