dc5ab1361faef145979cd94035f9d1c6a19ccd5b
[monky] / src / weather.c
1 /* Conky, a system monitor, based on torsmo
2  *
3  * Any original torsmo code is licensed under the BSD license
4  *
5  * All code written since the fork of torsmo is licensed under the GPL
6  *
7  * Please see COPYING for details
8  *
9  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
10  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
11  *      (see AUTHORS)
12  * All rights reserved.
13  *
14  * This program is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  *
26  */
27
28 #include "conky.h"
29 #include "logging.h"
30 #include "weather.h"
31 #include <time.h>
32 #include <ctype.h>
33 #include <curl/curl.h>
34 #include <curl/types.h>
35 #include <curl/easy.h>
36
37 #define MAX_LOCATIONS 3
38
39 /* Possible sky conditions */
40 #define NUM_CC_CODES 6
41 const char *CC_CODES[NUM_CC_CODES] =
42   {"SKC", "CLR", "FEW", "SCT", "BKN", "OVC"};
43
44 /* Possible weather modifiers */
45 #define NUM_WM_CODES 9
46 const char *WM_CODES[NUM_WM_CODES] =
47   {"VC", "MI", "BC", "PR", "TS", "BL", "SH", "DR", "FZ"};
48
49 /* Possible weather conditions */
50 #define NUM_WC_CODES 17
51 const char *WC_CODES[NUM_WC_CODES] =
52   {"DZ", "RA", "GR", "GS", "SN", "SG", "FG", "HZ", "FU", "BR", "DU", "SA",
53    "FC", "PO", "SQ", "SS", "DS"};
54
55 /*
56  * TODO: This could be made common with the one used in prss.c
57  *
58  */
59
60 struct WMemoryStruct {
61         char *memory;
62         size_t size;
63 };
64
65 typedef struct location_ {
66         char *uri;
67         int last_update;
68         PWEATHER *data;
69 } location;
70
71 int num_locations = 0;
72 location locations[MAX_LOCATIONS];
73
74 /*
75  * TODO: This could be made common with the one used in prss.c
76  *
77  */
78
79 size_t WWriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
80 {
81         size_t realsize = size * nmemb;
82         struct WMemoryStruct *mem = (struct WMemoryStruct *) data;
83
84         mem->memory = (char *) realloc(mem->memory, mem->size + realsize + 1);
85         if (mem->memory) {
86                 memcpy(&(mem->memory[mem->size]), ptr, realsize);
87                 mem->size += realsize;
88                 mem->memory[mem->size] = 0;
89         }
90         return realsize;
91 }
92
93 int weather_delay(int *last, int delay)
94 {
95         time_t now = time(NULL);
96
97         if ((!*last) || (now >= *last + delay)) {
98                 *last = now;
99                 return 1;
100         }
101
102         return 0;
103 }
104
105 void init_weather_info(void)
106 {
107         int i;
108
109         for (i = 0; i < MAX_LOCATIONS; i++) {
110                 locations[i].uri = NULL;
111                 locations[i].data = NULL;
112                 locations[i].last_update = 0;
113         }
114 }
115
116 void free_weather_info(void)
117 {
118         int i;
119
120         for (i = 0; i < num_locations; i++) {
121                 if (locations[i].uri != NULL) {
122                         free(locations[i].uri);
123                 }
124         }
125 }
126
127 int rel_humidity(int dew_point, int air) {
128   const float a = 17.27f;
129   const float b = 237.7f;
130
131   float g = a*dew_point/(b+dew_point);
132   return (int)(100.f*expf(g-a*air/(b+air)));
133 }
134
135 /*
136  * Horrible hack to avoid using regexes
137  *
138  */
139
140 static inline void parse_token(PWEATHER *res, char *token) {
141
142   int i;
143   char s_tmp[64];
144
145   switch (strlen(token)) {
146
147     //Check all tokens 2 chars long
148     case 2:
149
150       //Check if token is a weather condition
151       for (i=0; i<2; i++) {
152         if (!isalpha(token[i])) break;
153       }
154       if (i==2) {
155         for(i=0; i<NUM_WC_CODES; i++) {
156           if (!strncmp(token, WC_CODES[i], 2)) {
157             res->wc=i+1;
158             break;
159           }
160         }
161         return;
162       }
163
164       //Check for CB
165       if (!strcmp(token, "CB")) {
166         res->cc = 8;
167         return;
168       }
169     
170       break;
171
172     //Check all tokens 3 chars long
173     case 3:
174
175       //Check if token is a modified weather condition
176       if ((token[0] == '+') || (token[0] == '-')) {
177         for (i=1; i<3; i++) {
178           if (!isalpha(token[i])) break;
179         }
180         if (i==3) {
181           for(i=0; i<NUM_WC_CODES; i++) {
182             if (!strncmp(&token[1], WC_CODES[i], 2)) {
183               res->wc=i+1;
184               break;
185             }
186           }
187           return;
188         }
189       }
190
191       //Check for NCD or NSC
192       if ((!strcmp(token, "NCD")) || (!strcmp(token, "NSC"))) {
193         res->cc = 1;
194         return;
195       }
196
197       //Check for TCU
198       if (!strcmp(token, "TCU")) {
199         res->cc = 7;
200         return;
201       }
202
203       break;
204
205     //Check all tokens 4 chars long
206     case 4:
207
208       //Check if token is a modified weather condition
209       for(i=0; i<NUM_WM_CODES; i++) {
210         if (!strncmp(token, WM_CODES[i], 2)) {
211           for(i=0; i<NUM_WC_CODES; i++) {
212             if (!strncmp(&token[2], WC_CODES[i], 2)) {
213               res->wc=i+1;
214               return;
215             }
216           }
217           break;
218         }
219       }
220
221       break;
222
223     //Check all tokens 5 chars long
224     case 5:
225
226       //Check for CAVOK
227       if (!strcmp(token, "CAVOK")) {
228         res->cc = 1;
229         return;
230       }
231
232       //Check if token is the temperature
233       for (i=0; i<2; i++) {
234         if (!isdigit(token[i])) break;
235       }
236       if ((i==2) && (token[2] == '/')) {
237         for (i=3; i<5; i++) {
238           if (!isdigit(token[i])) break;
239         }
240         if (i==5) {
241           //First 2 digits gives the air temperature
242           res->tmpC=atoi(token);
243
244           //4th and 5th digits gives the dew point temperature
245           res->dew=atoi(&token[3]);
246
247           //Compute humidity
248           res->hmid = rel_humidity(res->dew, res->tmpC);
249
250           //Convert to Fahrenheit (faster here than in conky.c)
251           res->tmpF = (res->tmpC*9)/5 + 32;
252
253           return;
254         }
255       }
256
257       //Check if token is the pressure
258       if ((token[0] == 'Q') || (token[0] == 'A')) {
259         for (i=1; i<5; i++) {
260           if (!isdigit(token[i])) break;
261         }
262         if (i==5) {
263           if (token[0] == 'A') {
264             //Convert inches of mercury to mbar
265             res->bar = (int)(atoi(&token[1])*0.338637526f);
266             return;
267           }
268
269           //Last 4 digits is pressure im mbar
270           res->bar = atoi(&token[1]);
271           return;
272         }
273       }
274
275       //Check if token is a modified weather condition
276       if ((token[0] == '+') || (token[0] == '-')) {
277         for(i=0; i<NUM_WM_CODES; i++) {
278           if (!strncmp(&token[1], WM_CODES[i], 2)) {
279             for(i=0; i<NUM_WC_CODES; i++) {
280               if (!strncmp(&token[3], WC_CODES[i], 2)) {
281                 res->wc=i+1;
282                 return;
283               }
284             }
285             break;
286           }
287         }
288       }
289       break;
290
291     //Check all tokens 6 chars long
292     case 6:
293
294       //Check if token is the cloud cover
295       for (i=0; i<3; i++) {
296         if (!isalpha(token[i])) break;
297       }
298       if (i==3) {
299         for (i=3; i<6; i++) {
300           if (!isdigit(token[i])) break;
301         }
302         if (i==6) {
303           //Check if first 3 digits gives the cloud cover condition
304           for(i=0; i<NUM_CC_CODES; i++) {
305             if (!strncmp(token, CC_CODES[i], 3)) {
306               res->cc=i+1;
307               break;
308             }
309           }
310           return;
311         }
312       }
313
314       //Check if token is positive temp and negative dew
315       for (i=0; i<2; i++) {
316         if (!isdigit(token[i])) break;
317       }
318       if ((i==2) && (token[2] == '/')  && (token[3] == 'M')) {
319         for (i=4; i<6; i++) {
320           if (!isdigit(token[i])) break;
321         }
322         if (i==6) {
323           //1st and 2nd digits gives the temperature
324           res->tmpC = atoi(token);
325
326           //5th and 6th digits gives the dew point temperature
327           res->dew = -atoi(&token[4]);
328
329           //Compute humidity
330           res->hmid = rel_humidity(res->dew, res->tmpC);
331
332           //Convert to Fahrenheit (faster here than in conky.c)
333           res->tmpF = (res->tmpC*9)/5 + 32;
334
335           return;
336         }
337       }
338
339       break;
340
341     //Check all tokens 7 chars long
342     case 7:
343
344       //Check if token is the observation time
345       for (i=0; i<6; i++) {
346         if (!isdigit(token[i])) break;
347       }
348       if ((i==6) && (token[6] == 'Z')) return;
349
350       //Check if token is the wind speed/direction in knots
351       for (i=0; i<5; i++) {
352         if (!isdigit(token[i])) break;
353       }
354       if ((i==5) && (token[5] == 'K') &&  (token[6] == 'T')) {
355
356         //First 3 digits are wind direction
357         strncpy(s_tmp, token, 3);
358         res->wind_d=atoi(s_tmp);
359
360         //4th and 5th digit are wind speed in knots (convert to km/hr)
361         res->wind_s = (int)(atoi(&token[3])*1.852);
362
363         return;
364       }
365
366       //Check if token is negative temperature
367       if ((token[0] == 'M') && (token[4] == 'M')) {
368         for (i=1; i<3; i++) {
369           if (!isdigit(token[i])) break;
370         }
371         if ((i==3) && (token[3] == '/')) {
372           for (i=5; i<7; i++) {
373             if (!isdigit(token[i])) break;
374           }
375           if (i==7) {
376             //2nd and 3rd digits gives the temperature
377             res->tmpC = -atoi(&token[1]);
378
379             //6th and 7th digits gives the dew point temperature
380             res->dew = -atoi(&token[5]);
381
382             //Compute humidity
383             res->hmid = rel_humidity(res->dew, res->tmpC);
384
385             //Convert to Fahrenheit (faster here than in conky.c)
386             res->tmpF = (res->tmpC*9)/5 + 32;
387
388             return;
389           }
390         }
391       }
392
393       //Check if token is wind variability
394       for (i=0; i<3; i++) {
395         if (!isdigit(token[i])) break;
396       }
397       if ((i==3) && (token[3] == 'V')) {
398         for (i=4; i<7; i++) {
399           if (!isdigit(token[i])) break;
400         }
401         if (i==7) return;
402       }
403
404       break;
405
406     //Check all tokens 8 chars long
407     case 8:
408
409       //Check if token is the wind speed/direction in m/s
410       for (i=0; i<5; i++) {
411         if (!isdigit(token[i])) break;
412       }
413       if ((i==5)&&(token[5] == 'M')&&(token[6] == 'P')&&(token[7] == 'S')) {
414
415         //First 3 digits are wind direction
416         strncpy(s_tmp, token, 3);
417         res->wind_d=atoi(s_tmp);
418
419         //4th and 5th digit are wind speed in m/s (convert to km/hr)
420         res->wind_s = (int)(atoi(&token[3])*3.6);
421
422         return;
423       }
424
425     default:
426
427       //printf("token : %s\n", token);
428       break;
429     }
430 }
431
432 static inline PWEATHER *parse_weather(const char *data)
433 {
434   char s_tmp[256];
435   const char delim[] = " ";
436
437   PWEATHER *res = malloc(sizeof(PWEATHER));
438   memset(res, 0, sizeof(PWEATHER));
439
440   //Divide time stamp and metar data
441   if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
442     
443     //Process all tokens
444     char *p_tok = NULL;
445     char *p_save = NULL;
446
447     if ((strtok_r(s_tmp, delim, &p_save)) != NULL) {
448
449       //Jump first token, must be icao
450       p_tok = strtok_r(NULL, delim, &p_save);
451
452       do {
453
454         parse_token(res, p_tok);
455         p_tok = strtok_r(NULL, delim, &p_save);
456       
457       } while (p_tok != NULL);
458     }
459     return res;
460   }
461   else {
462     return NULL;
463   }
464 }
465
466 PWEATHER *get_weather_info(char *uri, int delay)
467 {
468   CURL *curl = NULL;
469   CURLcode res;
470
471   // pointers to struct
472   location *curloc = NULL;
473   PWEATHER *curdata = NULL;
474   int *last_update = 0;
475
476   int i;
477
478   // curl temps
479   struct WMemoryStruct chunk;
480
481   chunk.memory = NULL;
482   chunk.size = 0;
483
484   // first seek for the uri in list
485   for (i = 0; i < num_locations; i++) {
486     if (locations[i].uri != NULL) {
487       if (!strcmp(locations[i].uri, uri)) {
488         curloc = &locations[i];
489         break;
490       }
491     }
492   }
493
494   if (!curloc) { // new location
495     if (num_locations == MAX_LOCATIONS) {
496       return NULL;
497     }
498     curloc = &locations[num_locations];
499     curloc->uri = strndup(uri, text_buffer_size);
500     num_locations++;
501   }
502
503   last_update = &curloc->last_update;
504   curdata = curloc->data;
505
506   // wait for delay to pass
507   if (!weather_delay(last_update, delay)) {
508     return curdata;
509   }
510
511   // clean up old data
512   if (curdata != NULL) {
513     free(curdata);
514     curdata = NULL;
515   }
516
517   curl = curl_easy_init();
518   if (curl) {
519     curl_easy_setopt(curl, CURLOPT_URL, uri);
520     curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
521     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WWriteMemoryCallback);
522     curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &chunk);
523     curl_easy_setopt(curl, CURLOPT_USERAGENT, "conky-weather/1.0");
524
525     res = curl_easy_perform(curl);
526     if (chunk.size) {
527       curdata = parse_weather(chunk.memory);
528       free(chunk.memory);
529     } else {
530       ERR("No data from server");
531     }
532     
533     curl_easy_cleanup(curl);
534   }
535
536   curloc->data = curdata;
537
538   return curdata;
539 }