984a26136c0119f28154f4b4232a17f2537edffd
[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 7
41 const char *CC_CODES[NUM_CC_CODES] =
42   {"SKC", "CLR", "FEW", "SCT", "BKN", "OVC", "TCU"};
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   //Check all tokens 2 chars long
141   if (strlen(token) == 2 ) {
142
143     //Check if token is a weather condition
144     for (i=0; i<2; i++) {
145       if (!isalpha(token[i])) break;
146     }
147     if (i==2) {
148       for(i=0; i<NUM_WC_CODES; i++) {
149         if (!strncmp(token, WC_CODES[i], 2)) {
150           res->wc=i+1;
151           break;
152         }
153       }
154       return;
155     }
156
157     //Check for CB
158     if (!strcmp(token, "CB")) {
159       res->cc = 8;
160       return;
161     }
162
163   }
164
165   //Check all tokens 3 chars long
166   if (strlen(token) == 3 ) {
167
168     //Check if token is a modified weather condition
169     if ((token[0] == '+') || (token[0] == '-')) {
170       for (i=1; i<3; i++) {
171         if (!isalpha(token[i])) break;
172       }
173       if (i==3) {
174         for(i=0; i<NUM_WC_CODES; i++) {
175           if (!strncmp(&token[1], WC_CODES[i], 2)) {
176             res->wc=i+1;
177             break;
178           }
179         }
180         return;
181       }
182     }
183
184     //Check for NCD
185     if (!strcmp(token, "NCD")) {
186       res->cc = 1;
187       return;
188     }
189
190   }
191
192   //Check all tokens 4 chars long
193   if (strlen(token) == 4 ) {
194
195     //Check if token is an icao
196     for (i=0; i<4; i++) {
197       if (!isalpha(token[i])) break;
198     }
199     if (i==4) return;
200
201   }
202
203   //Check all tokens 5 chars long
204   if (strlen(token) == 5 ) {
205
206     //Check for CAVOK
207     if (!strcmp(token, "CAVOK")) {
208       res->cc = 1;
209       return;
210     }
211
212     //Check if token is the temperature
213     for (i=0; i<2; i++) {
214       if (!isdigit(token[i])) break;
215     }
216     if ((i==2) && (token[2] == '/')) {
217       for (i=3; i<5; i++) {
218         if (!isdigit(token[i])) break;
219       }
220       if (i==5) {
221         //First 2 digits gives the air temperature
222         res->tmpC=atoi(token);
223
224         //4th and 5th digits gives the dew point temperature
225         res->dew=atoi(&token[3]);
226
227         //Compute humidity
228         res->hmid = rel_humidity(res->dew, res->tmpC);
229
230         //Convert to Fahrenheit (faster here than in conky.c)
231         res->tmpF = (res->tmpC*9)/5 + 32;
232
233         return;
234       }
235     }
236
237     //Check if token is the pressure
238     if ((token[0] == 'Q') || (token[0] == 'A')) {
239       for (i=1; i<5; i++) {
240         if (!isdigit(token[i])) break;
241       }
242       if (i==5) {
243         if (token[0] == 'A') {
244           //Convert inches of mercury to mbar
245           res->bar = (int)(atoi(&token[1])*0.338637526f);
246           return;
247         }
248
249         //Last 4 digits is pressure im mbar
250         res->bar = atoi(&token[1]);
251         return;
252       }
253     }
254   }
255
256   //Check all tokens 6 chars long
257   if (strlen(token) == 6 ) {
258
259     //Check if token is the cloud cover
260     for (i=0; i<3; i++) {
261       if (!isalpha(token[i])) break;
262     }
263     if (i==3) {
264       for (i=3; i<6; i++) {
265         if (!isdigit(token[i])) break;
266       }
267       if (i==6) {
268         //Check if first 3 digits gives the cloud cover condition
269         for(i=0; i<NUM_CC_CODES; i++) {
270           if (!strncmp(token, CC_CODES[i], 3)) {
271             res->cc=i+1;
272             break;
273           }
274         }
275         return;
276       }
277     }
278
279     //Check if token is positive temp and negative dew
280     for (i=0; i<2; i++) {
281       if (!isdigit(token[i])) break;
282     }
283     if ((i==2) && (token[2] == '/')  && (token[3] == 'M')) {
284       for (i=4; i<6; i++) {
285         if (!isdigit(token[i])) break;
286       }
287       if (i==6) {
288           //1st and 2nd digits gives the temperature
289           res->tmpC = atoi(token);
290
291           //5th and 6th digits gives the dew point temperature
292           res->dew = -atoi(&token[4]);
293
294           //Compute humidity
295           res->hmid = rel_humidity(res->dew, res->tmpC);
296
297           //Convert to Fahrenheit (faster here than in conky.c)
298           res->tmpF = (res->tmpC*9)/5 + 32;
299
300           return;
301         }
302     }
303   }
304
305   //Check all tokens 7 chars long
306   if (strlen(token) == 7 ) {
307
308     //Check if token is the observation time
309     for (i=0; i<6; i++) {
310       if (!isdigit(token[i])) break;
311     }
312     if ((i==6) && (token[6] == 'Z')) return;
313
314     //Check if token is the wind speed/direction in knots
315     for (i=0; i<5; i++) {
316       if (!isdigit(token[i])) break;
317     }
318     if ((i==5) && (token[5] == 'K') &&  (token[6] == 'T')) {
319
320       //First 3 digits are wind direction
321       strncpy(s_tmp, token, 3);
322       res->wind_d=atoi(s_tmp);
323
324       //4th and 5th digit are wind speed in knots (convert to km/hr)
325       res->wind_s = (int)(atoi(&token[3])*1.852);
326
327       return;
328     }
329
330     //Check if token is negative temperature
331     if ((token[0] == 'M') && (token[4] == 'M')) {
332       for (i=1; i<3; i++) {
333         if (!isdigit(token[i])) break;
334       }
335     if ((i==3) && (token[3] == '/')) {
336         for (i=5; i<7; i++) {
337           if (!isdigit(token[i])) break;
338         }
339         if (i==7) {
340           //2nd and 3rd digits gives the temperature
341           res->tmpC = -atoi(&token[1]);
342
343           //6th and 7th digits gives the dew point temperature
344           res->dew = -atoi(&token[5]);
345
346           //Compute humidity
347           res->hmid = rel_humidity(res->dew, res->tmpC);
348
349           //Convert to Fahrenheit (faster here than in conky.c)
350           res->tmpF = (res->tmpC*9)/5 + 32;
351
352           return;
353         }
354       }
355     }
356
357     //Check if token is wind variability
358     for (i=0; i<3; i++) {
359       if (!isdigit(token[i])) break;
360     }
361     if ((i==3) && (token[3] == 'V')) {
362       for (i=4; i<7; i++) {
363         if (!isdigit(token[i])) break;
364       }
365       if (i==7) return;
366     }
367
368   }
369
370   //Check all tokens 8 chars long
371   if (strlen(token) == 8 ) {
372
373     //Check if token is the wind speed/direction in m/s
374     for (i=0; i<5; i++) {
375       if (!isdigit(token[i])) break;
376     }
377     if ((i==5)&&(token[5] == 'M')&&(token[6] == 'P')&&(token[7] == 'S')) {
378
379       //First 3 digits are wind direction
380       strncpy(s_tmp, token, 3);
381       res->wind_d=atoi(s_tmp);
382
383       //4th and 5th digit are wind speed in m/s (convert to km/hr)
384       res->wind_s = (int)(atoi(&token[3])*3.6);
385
386       return;
387     }
388
389   }
390
391   //printf("token : %s\n", token);
392 }
393
394 static inline PWEATHER *parse_weather(const char *data)
395 {
396   char s_tmp[256];
397   const char delim[] = " ";
398
399   PWEATHER *res = malloc(sizeof(PWEATHER));
400   memset(res, 0, sizeof(PWEATHER));
401
402   //Divide time stamp and metar data
403   if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
404     
405     //Process all tokens
406     char *p_tok = NULL;
407     char *p_save = NULL;
408
409     if ((p_tok = strtok_r(s_tmp, delim, &p_save)) != NULL) {
410       do {
411
412         parse_token(res, p_tok);
413         p_tok = strtok_r(NULL, delim, &p_save);
414       
415       } while (p_tok != NULL);
416     }
417       return res;
418   }
419   else {
420     return NULL;
421   }
422 }
423
424 PWEATHER *get_weather_info(char *uri, int delay)
425 {
426   CURL *curl = NULL;
427   CURLcode res;
428
429   // pointers to struct
430   location *curloc = NULL;
431   PWEATHER *curdata = NULL;
432   int *last_update = 0;
433
434   int i;
435
436   // curl temps
437   struct WMemoryStruct chunk;
438
439   chunk.memory = NULL;
440   chunk.size = 0;
441
442   // first seek for the uri in list
443   for (i = 0; i < num_locations; i++) {
444     if (locations[i].uri != NULL) {
445       if (!strcmp(locations[i].uri, uri)) {
446         curloc = &locations[i];
447         break;
448       }
449     }
450   }
451
452   if (!curloc) { // new location
453     if (num_locations == MAX_LOCATIONS) {
454       return NULL;
455     }
456     curloc = &locations[num_locations];
457     curloc->uri = strndup(uri, text_buffer_size);
458     num_locations++;
459   }
460
461   last_update = &curloc->last_update;
462   curdata = curloc->data;
463
464   // wait for delay to pass
465   if (!weather_delay(last_update, delay)) {
466     return curdata;
467   }
468
469   // clean up old data
470   if (curdata != NULL) {
471     free(curdata);
472     curdata = NULL;
473   }
474
475   curl = curl_easy_init();
476   if (curl) {
477     curl_easy_setopt(curl, CURLOPT_URL, uri);
478     curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
479     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WWriteMemoryCallback);
480     curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &chunk);
481     curl_easy_setopt(curl, CURLOPT_USERAGENT, "conky-weather/1.0");
482
483     res = curl_easy_perform(curl);
484     if (chunk.size) {
485       curdata = parse_weather(chunk.memory);
486       free(chunk.memory);
487     } else {
488       ERR("No data from server");
489     }
490     
491     curl_easy_cleanup(curl);
492   }
493
494   curloc->data = curdata;
495
496   return curdata;
497 }