5598602fc8bfea1b93a125d6a5bd6357c33b31a6
[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 conditions */
45 #define NUM_WC_CODES 17
46 const char *WC_CODES[NUM_WC_CODES] =
47   {"DZ", "RA", "GR", "GS", "SN", "SG", "FG", "HZ", "FU", "BR", "DU", "SA",
48    "FC", "PO", "SQ", "SS", "DS"};
49
50 /*
51  * TODO: This could be made common with the one used in prss.c
52  *
53  */
54
55 struct WMemoryStruct {
56         char *memory;
57         size_t size;
58 };
59
60 typedef struct location_ {
61         char *uri;
62         int last_update;
63         PWEATHER *data;
64 } location;
65
66 int num_locations = 0;
67 location locations[MAX_LOCATIONS];
68
69 /*
70  * TODO: This could be made common with the one used in prss.c
71  *
72  */
73
74 size_t WWriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
75 {
76         size_t realsize = size * nmemb;
77         struct WMemoryStruct *mem = (struct WMemoryStruct *) data;
78
79         mem->memory = (char *) realloc(mem->memory, mem->size + realsize + 1);
80         if (mem->memory) {
81                 memcpy(&(mem->memory[mem->size]), ptr, realsize);
82                 mem->size += realsize;
83                 mem->memory[mem->size] = 0;
84         }
85         return realsize;
86 }
87
88 int weather_delay(int *last, int delay)
89 {
90         time_t now = time(NULL);
91
92         if ((!*last) || (now >= *last + delay)) {
93                 *last = now;
94                 return 1;
95         }
96
97         return 0;
98 }
99
100 void init_weather_info(void)
101 {
102         int i;
103
104         for (i = 0; i < MAX_LOCATIONS; i++) {
105                 locations[i].uri = NULL;
106                 locations[i].data = NULL;
107                 locations[i].last_update = 0;
108         }
109 }
110
111 void free_weather_info(void)
112 {
113         int i;
114
115         for (i = 0; i < num_locations; i++) {
116                 if (locations[i].uri != NULL) {
117                         free(locations[i].uri);
118                 }
119         }
120 }
121
122 int rel_humidity(int dew_point, int air) {
123   const float a = 17.27f;
124   const float b = 237.7f;
125
126   float g = a*dew_point/(b+dew_point);
127   return (int)(100.f*expf(g-a*air/(b+air)));
128 }
129
130 /*
131  * Horrible hack to avoid using regexes
132  *
133  */
134
135 static inline void parse_token(PWEATHER *res, char *token) {
136
137   int i;
138   char s_tmp[64];
139
140   switch (strlen(token)) {
141
142     //Check all tokens 2 chars long
143     case 2:
144
145       //Check if token is a weather condition
146       for (i=0; i<2; i++) {
147         if (!isalpha(token[i])) break;
148       }
149       if (i==2) {
150         for(i=0; i<NUM_WC_CODES; i++) {
151           if (!strncmp(token, WC_CODES[i], 2)) {
152             res->wc=i+1;
153             break;
154           }
155         }
156         return;
157       }
158
159       //Check for CB
160       if (!strcmp(token, "CB")) {
161         res->cc = 8;
162         return;
163       }
164     
165       break;
166
167     //Check all tokens 3 chars long
168     case 3:
169
170       //Check if token is a modified weather condition
171       if ((token[0] == '+') || (token[0] == '-')) {
172         for (i=1; i<3; i++) {
173           if (!isalpha(token[i])) break;
174         }
175         if (i==3) {
176           for(i=0; i<NUM_WC_CODES; i++) {
177             if (!strncmp(&token[1], WC_CODES[i], 2)) {
178               res->wc=i+1;
179               break;
180             }
181           }
182           return;
183         }
184       }
185
186       //Check for NCD or NCD
187       if ((!strcmp(token, "NCD")) || (!strcmp(token, "NSC"))) {
188         res->cc = 1;
189         return;
190       }
191
192       //Check for TCU
193       if (!strcmp(token, "TCU")) {
194         res->cc = 7;
195         return;
196       }
197
198       break;
199
200     //Check all tokens 4 chars long
201     case 4:
202
203       //Check if token is an icao
204       for (i=0; i<4; i++) {
205         if (!isalpha(token[i])) break;
206       }
207       if (i==4) return;
208
209       break;
210
211     //Check all tokens 5 chars long
212     case 5:
213
214       //Check for CAVOK
215       if (!strcmp(token, "CAVOK")) {
216         res->cc = 1;
217         return;
218       }
219
220       //Check if token is the temperature
221       for (i=0; i<2; i++) {
222         if (!isdigit(token[i])) break;
223       }
224       if ((i==2) && (token[2] == '/')) {
225         for (i=3; i<5; i++) {
226           if (!isdigit(token[i])) break;
227         }
228         if (i==5) {
229           //First 2 digits gives the air temperature
230           res->tmpC=atoi(token);
231
232           //4th and 5th digits gives the dew point temperature
233           res->dew=atoi(&token[3]);
234
235           //Compute humidity
236           res->hmid = rel_humidity(res->dew, res->tmpC);
237
238           //Convert to Fahrenheit (faster here than in conky.c)
239           res->tmpF = (res->tmpC*9)/5 + 32;
240
241           return;
242         }
243       }
244
245       //Check if token is the pressure
246       if ((token[0] == 'Q') || (token[0] == 'A')) {
247         for (i=1; i<5; i++) {
248           if (!isdigit(token[i])) break;
249         }
250         if (i==5) {
251           if (token[0] == 'A') {
252             //Convert inches of mercury to mbar
253             res->bar = (int)(atoi(&token[1])*0.338637526f);
254             return;
255           }
256
257           //Last 4 digits is pressure im mbar
258           res->bar = atoi(&token[1]);
259           return;
260         }
261       }
262
263       break;
264
265     //Check all tokens 6 chars long
266     case 6:
267
268       //Check if token is the cloud cover
269       for (i=0; i<3; i++) {
270         if (!isalpha(token[i])) break;
271       }
272       if (i==3) {
273         for (i=3; i<6; i++) {
274           if (!isdigit(token[i])) break;
275         }
276         if (i==6) {
277           //Check if first 3 digits gives the cloud cover condition
278           for(i=0; i<NUM_CC_CODES; i++) {
279             if (!strncmp(token, CC_CODES[i], 3)) {
280               res->cc=i+1;
281               break;
282             }
283           }
284           return;
285         }
286       }
287
288       //Check if token is positive temp and negative dew
289       for (i=0; i<2; i++) {
290         if (!isdigit(token[i])) break;
291       }
292       if ((i==2) && (token[2] == '/')  && (token[3] == 'M')) {
293         for (i=4; i<6; i++) {
294           if (!isdigit(token[i])) break;
295         }
296         if (i==6) {
297           //1st and 2nd digits gives the temperature
298           res->tmpC = atoi(token);
299
300           //5th and 6th digits gives the dew point temperature
301           res->dew = -atoi(&token[4]);
302
303           //Compute humidity
304           res->hmid = rel_humidity(res->dew, res->tmpC);
305
306           //Convert to Fahrenheit (faster here than in conky.c)
307           res->tmpF = (res->tmpC*9)/5 + 32;
308
309           return;
310         }
311       }
312
313       break;
314
315     //Check all tokens 7 chars long
316     case 7:
317
318       //Check if token is the observation time
319       for (i=0; i<6; i++) {
320         if (!isdigit(token[i])) break;
321       }
322       if ((i==6) && (token[6] == 'Z')) return;
323
324       //Check if token is the wind speed/direction in knots
325       for (i=0; i<5; i++) {
326         if (!isdigit(token[i])) break;
327       }
328       if ((i==5) && (token[5] == 'K') &&  (token[6] == 'T')) {
329
330         //First 3 digits are wind direction
331         strncpy(s_tmp, token, 3);
332         res->wind_d=atoi(s_tmp);
333
334         //4th and 5th digit are wind speed in knots (convert to km/hr)
335         res->wind_s = (int)(atoi(&token[3])*1.852);
336
337         return;
338       }
339
340       //Check if token is negative temperature
341       if ((token[0] == 'M') && (token[4] == 'M')) {
342         for (i=1; i<3; i++) {
343           if (!isdigit(token[i])) break;
344         }
345         if ((i==3) && (token[3] == '/')) {
346           for (i=5; i<7; i++) {
347             if (!isdigit(token[i])) break;
348           }
349           if (i==7) {
350             //2nd and 3rd digits gives the temperature
351             res->tmpC = -atoi(&token[1]);
352
353             //6th and 7th digits gives the dew point temperature
354             res->dew = -atoi(&token[5]);
355
356             //Compute humidity
357             res->hmid = rel_humidity(res->dew, res->tmpC);
358
359             //Convert to Fahrenheit (faster here than in conky.c)
360             res->tmpF = (res->tmpC*9)/5 + 32;
361
362             return;
363           }
364         }
365       }
366
367       //Check if token is wind variability
368       for (i=0; i<3; i++) {
369         if (!isdigit(token[i])) break;
370       }
371       if ((i==3) && (token[3] == 'V')) {
372         for (i=4; i<7; i++) {
373           if (!isdigit(token[i])) break;
374         }
375         if (i==7) return;
376       }
377
378       break;
379
380     //Check all tokens 8 chars long
381     case 8:
382
383       //Check if token is the wind speed/direction in m/s
384       for (i=0; i<5; i++) {
385         if (!isdigit(token[i])) break;
386       }
387       if ((i==5)&&(token[5] == 'M')&&(token[6] == 'P')&&(token[7] == 'S')) {
388
389         //First 3 digits are wind direction
390         strncpy(s_tmp, token, 3);
391         res->wind_d=atoi(s_tmp);
392
393         //4th and 5th digit are wind speed in m/s (convert to km/hr)
394         res->wind_s = (int)(atoi(&token[3])*3.6);
395
396         return;
397       }
398
399     default:
400
401       //printf("token : %s\n", token);
402       break;
403     }
404 }
405
406 static inline PWEATHER *parse_weather(const char *data)
407 {
408   char s_tmp[256];
409   const char delim[] = " ";
410
411   PWEATHER *res = malloc(sizeof(PWEATHER));
412   memset(res, 0, sizeof(PWEATHER));
413
414   //Divide time stamp and metar data
415   if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
416     
417     //Process all tokens
418     char *p_tok = NULL;
419     char *p_save = NULL;
420
421     if ((p_tok = strtok_r(s_tmp, delim, &p_save)) != NULL) {
422       do {
423
424         parse_token(res, p_tok);
425         p_tok = strtok_r(NULL, delim, &p_save);
426       
427       } while (p_tok != NULL);
428     }
429       return res;
430   }
431   else {
432     return NULL;
433   }
434 }
435
436 PWEATHER *get_weather_info(char *uri, int delay)
437 {
438   CURL *curl = NULL;
439   CURLcode res;
440
441   // pointers to struct
442   location *curloc = NULL;
443   PWEATHER *curdata = NULL;
444   int *last_update = 0;
445
446   int i;
447
448   // curl temps
449   struct WMemoryStruct chunk;
450
451   chunk.memory = NULL;
452   chunk.size = 0;
453
454   // first seek for the uri in list
455   for (i = 0; i < num_locations; i++) {
456     if (locations[i].uri != NULL) {
457       if (!strcmp(locations[i].uri, uri)) {
458         curloc = &locations[i];
459         break;
460       }
461     }
462   }
463
464   if (!curloc) { // new location
465     if (num_locations == MAX_LOCATIONS) {
466       return NULL;
467     }
468     curloc = &locations[num_locations];
469     curloc->uri = strndup(uri, text_buffer_size);
470     num_locations++;
471   }
472
473   last_update = &curloc->last_update;
474   curdata = curloc->data;
475
476   // wait for delay to pass
477   if (!weather_delay(last_update, delay)) {
478     return curdata;
479   }
480
481   // clean up old data
482   if (curdata != NULL) {
483     free(curdata);
484     curdata = NULL;
485   }
486
487   curl = curl_easy_init();
488   if (curl) {
489     curl_easy_setopt(curl, CURLOPT_URL, uri);
490     curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
491     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WWriteMemoryCallback);
492     curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &chunk);
493     curl_easy_setopt(curl, CURLOPT_USERAGENT, "conky-weather/1.0");
494
495     res = curl_easy_perform(curl);
496     if (chunk.size) {
497       curdata = parse_weather(chunk.memory);
498       free(chunk.memory);
499     } else {
500       ERR("No data from server");
501     }
502     
503     curl_easy_cleanup(curl);
504   }
505
506   curloc->data = curdata;
507
508   return curdata;
509 }