Added www.weather.com as an additional source of weather data
authorCesare Tirabassi <norsetto@ubuntu.com>
Sat, 18 Jul 2009 13:59:43 +0000 (15:59 +0200)
committerCesare Tirabassi <norsetto@ubuntu.com>
Sat, 18 Jul 2009 13:59:43 +0000 (15:59 +0200)
ChangeLog
configure.ac.in
doc/variables.xml
src/conky.c
src/weather.c
src/weather.h

index d574678..8827a7c 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+2009-07-18
+       * www.weather.com can now be used as well as a source of weather data
+
 2009-07-11
        * Added support for $desktop, $desktop_number and $desktop_name (sf.net #2040528)
 
index a56bd6d..9c9facd 100644 (file)
@@ -356,9 +356,10 @@ AC_ARG_ENABLE([weather],
 #
 AM_CONDITIONAL(BUILD_WEATHER, test x$want_weather = xyes)
 if test x$want_weather = xyes; then
+       PKG_CHECK_MODULES([libxml2], libxml-2.0)
        PKG_CHECK_MODULES([libcurl], libcurl)
-       conky_CFLAGS="$conky_CFLAGS $libcurl_CFLAGS"
-       conky_LIBS="$conky_LIBS $libcurl_LIBS"
+       conky_CFLAGS="$conky_CFLAGS $libxml2_CFLAGS $libcurl_CFLAGS"
+       conky_LIBS="$conky_LIBS $libxml2_LIBS $libcurl_LIBS"
        AC_DEFINE(WEATHER, 1, [Define if you want weather support])
 fi
 
@@ -706,6 +707,7 @@ dnl
 
 AC_DEFINE(DEFAULTNETDEV, "eth0", [Default networkdevice])
 AC_DEFINE(CONFIG_FILE, "$HOME/.conkyrc", [Configfile of the user])
+AC_DEFINE(XOAP_FILE, "$HOME/.xoaprc", [User xoap keys file])
 AC_DEFINE(MAX_SPECIALS_DEFAULT, 512, [Default maximum number of special things, e.g. fonts, offsets, aligns, etc.])
 AC_DEFINE(MAX_USER_TEXT_DEFAULT, 16384, [Default maximum size of config TEXT buffer, i.e. below TEXT line.])
 AC_DEFINE(DEFAULT_TEXT_BUFFER_SIZE, 256, [Default size used for temporary, static text buffers])
index 214a959..0c69c85 100644 (file)
             <command>
                 <option>weather</option>
             </command>
-            <option>URI icao data_type (delay_in_minutes)</option>
+            <option>URI locID data_type (delay_in_minutes)</option>
         </term>
         <listitem>
             <para>Download, parse and display METAR data.</para>
-           <para>For the 'URI', right now only 
-           http://weather.noaa.gov/pub/data/observations/metar/stations/
-            is supported. Other sources might be supported in the future.
+           <para>For the 'URI', there are two possibilities:</para>
+           <simplelist>
+               <member>
+                   http://weather.noaa.gov/pub/data/observations/metar/stations/
+               </member>
+               <member>
+                   http://xoap.weather.com/weather/local/
+               </member>
+           </simplelist>
+           <para>The first one is free to use but the second requires you to
+           register and obtain your partner ID and license key.
+           These two must be written, separated by a space, into a file
+           called .xoaprc which needs to be placed into your home directory.
            </para>
-            <para>'icao' must be a valid icao for the required location
+            <para>'locID' must be a valid location identifier for the required
+           uri. For the NOAA site this must be a valid ICAO
             (see for instance https://pilotweb.nas.faa.gov/qryhtml/icao/).
+           For the weather.com site this must be a valid location ID
+            (see for instance http://aspnetresources.com/tools/locid.aspx).
             </para>
             <para>'data_type' must be one of the following:</para>
             <simplelist>
                 <member>
                     <command>last_update</command>
-                    <option>The date (yyyy/mm/dd) and time (UTC) of
-                    the last update</option>
+                    <option>The date and time stamp of the data.
+                   The result depends on the URI used. For the NOAA site
+                   it is date (yyyy/mm/dd) and UTC time. For the
+                   weather.com one it is date ([m]m/[d]d/yy) and Local Time
+                   of the station.
+                   </option>
                 </member>
                 <member>
                     <command>temperature</command>
                     <option>The highest cloud cover status</option>
                 </member>
                 <member>
-                    <command>pressurer</command>
+                    <command>pressure</command>
                     <option>Air pressure in millibar</option>
                 </member>
                 <member>
                 <member>
                     <command>weather</command>
                     <option>Any relevant weather event (rain, snow,
-                    etc.))</option>
+                    etc.). This is not used if you are querying the
+                   weather.com site since this data is aggregated
+                   into the cloud_cover one</option>
                 </member>
             </simplelist>
             <para>'delay_in_minutes' (optional, default 30) cannot be
index 8f47d29..d944885 100644 (file)
@@ -318,6 +318,11 @@ static int cpu_avg_samples, net_avg_samples, diskio_avg_samples;
 char *overwrite_file = NULL; FILE *overwrite_fpointer = NULL;
 char *append_file = NULL; FILE *append_fpointer = NULL;
 
+/* xoap suffix for weather from weather.com */
+#ifdef WEATHER
+static char *xoap = NULL;
+#endif /* WEATHER */
+
 #ifdef X11
 
 static int show_graph_scale;
@@ -2820,35 +2825,53 @@ static struct text_object *construct_text_object(const char *s,
        END OBJ_THREAD(weather, 0)
                if (arg) {
                        int argc, interval;
-                       char *icao = (char *) malloc(5 * sizeof(char));
+                       char *locID = (char *) malloc(9 * sizeof(char));
                        char *uri = (char *) malloc(128 * sizeof(char));
                        char *data_type = (char *) malloc(32 * sizeof(char));
                        char *tmp_p;
 
-                       argc = sscanf(arg, "%119s %4s %31s %d", uri, icao, data_type, &interval);
+                       argc = sscanf(arg, "%119s %8s %31s %d", uri, locID, data_type, &interval);
 
-                       //icao MUST BE upper-case
-                       tmp_p = icao;
+                       //locID MUST BE upper-case
+                       tmp_p = locID;
                        while (*tmp_p) {
-                               *tmp_p = toupper(*tmp_p);
-                               tmp_p++;
+                         *tmp_p = toupper(*tmp_p);
+                         tmp_p++;
+                       }
+
+                       //Construct complete uri
+                       if (strstr(uri, "xoap.weather.com")) {
+                         if(xoap != NULL) {
+                           strcat(uri, locID);
+                           strcat(uri, xoap);
+                         } else {
+                           free(uri);
+                           uri = NULL;
+                         }
+                       } else if (strstr(uri, "weather.noaa.gov")) {
+                           strcat(uri, locID);
+                           strcat(uri, ".TXT");
+                       } else  if (!strstr(uri, "localhost") && !strstr(uri, "127.0.0.1")) {
+                             CRIT_ERR(obj, free_at_crash, \
+                                      "could not recognize the weather uri");
                        }
 
-
-                       strcat(uri, icao);
-                       strcat(uri, ".TXT");
                        obj->data.weather.uri = uri;
-
                        obj->data.weather.data_type = data_type;
 
-                       // The data retrieval interval is limited to half an hour
+                       //Limit the data retrieval interval to half hour min
                        if (interval < 30) {
                                interval = 30;
                        }
-                       obj->data.weather.interval = interval * 60; // convert to seconds
-                       free(icao);
+
+                       //Convert to seconds
+                       obj->data.weather.interval = interval * 60;
+                       free(locID);
+
+                       DBGP("weather: fetching %s from %s every %d seconds", \
+                            data_type, uri, obj->data.weather.interval);
                } else {
-                       CRIT_ERR(obj, free_at_crash, "weather needs arguments: <uri> <icao> <data_type> [interval in minutes]");
+                       CRIT_ERR(obj, free_at_crash, "weather needs arguments: <uri> <locID> <data_type> [interval in minutes]");
                }
 #endif
 #ifdef HAVE_LUA
@@ -4647,7 +4670,11 @@ static void generate_text_internal(char *p, int p_max_size,
 #endif
 #ifdef WEATHER
                        OBJ(weather) {
-                               process_weather_info(p, p_max_size, obj->data.weather.uri, obj->data.weather.data_type, obj->data.weather.interval);
+                               if( obj->data.weather.uri != NULL ) {
+                                       process_weather_info(p, p_max_size, obj->data.weather.uri, obj->data.weather.data_type, obj->data.weather.interval);
+                               } else {
+                                 strncpy(p, "invalid xoap keys file",  p_max_size);
+                               }
                        }
 #endif
 #ifdef HAVE_LUA
@@ -8884,6 +8911,39 @@ static void load_config_file_x11(const char *f)
 }
 #endif /* X11 */
 
+/*
+ * TODO: make the xoap keys file readable from the config file
+ *       make the keys directly readable from the config file
+ *       make the xoap keys file giveable as a command line option
+ */
+static void load_xoap_keys(void) {
+  FILE *fp;
+  char *par = (char *) malloc(11 * sizeof(char));
+  char *key = (char *) malloc(17 * sizeof(char));
+
+  xoap = (char *) malloc(64 * sizeof(char));
+  to_real_path(xoap, XOAP_FILE);
+  fp = fopen(xoap, "r");
+  if (fp != NULL) {
+    if( fscanf(fp, "%10s %16s", par, key) == 2 ) {
+      strcpy(xoap, "?cc=*&link=xoap&prod=xoap&par=");
+      strcat(xoap, par);
+      strcat(xoap, "&key=");
+      strcat(xoap, key);
+      strcat(xoap, "&unit=m");
+    } else {
+      free(xoap);
+      xoap = NULL;
+    }
+    fclose(fp);
+  } else {
+    free(xoap);
+    xoap = NULL;
+  }
+  free(par);
+  free(key);
+}
+
 static void print_help(const char *prog_name) {
        printf("Usage: %s [OPTION]...\n"
                        PACKAGE_NAME" is a system monitor that renders text on desktop or to own transparent\n"
@@ -9267,6 +9327,12 @@ int main(int argc, char **argv)
 #endif /* ! CONF_OUTPUT */
                }
        }
+
+#ifdef WEATHER
+       /* Load xoap keys, if existing */
+       load_xoap_keys();
+#endif /* WEATHER */
+
 #ifdef HAVE_SYS_INOTIFY_H
        inotify_fd = inotify_init();
 #endif /* HAVE_SYS_INOTIFY_H */
index 361bdfb..6d29027 100644 (file)
  *
  */
 
+/*
+ * TODO: Add weather forecast info from weather.com
+ *
+ */
+
 #include "conky.h"
 #include "logging.h"
 #include "weather.h"
@@ -33,6 +38,7 @@
 #include <curl/curl.h>
 #include <curl/types.h>
 #include <curl/easy.h>
+#include <libxml/parser.h>
 
 /* Possible sky conditions */
 #define NUM_CC_CODES 6
@@ -120,6 +126,75 @@ int rel_humidity(int dew_point, int air) {
 #endif /* MATH */
 }
 
+//TODO: Lets get rid of the recursion
+static void parse_cc(PWEATHER *res, xmlNodePtr cc)
+{
+
+  xmlNodePtr cur = NULL;
+
+  for (cur = cc; cur; cur = cur->next) {
+    if (cur->type == XML_ELEMENT_NODE) {
+      if (!xmlStrcmp(cur->name, (const xmlChar *) "lsup")) {
+       strncpy(res->lastupd, (char *)cur->children->content, 31);
+      } else if        (!xmlStrcmp(cur->name, (const xmlChar *) "tmp")) {
+       res->temp = atoi((char *)cur->children->content);
+      } else if (!xmlStrcmp(cur->name, (const xmlChar *) "t")) {
+       if(res->xoap_t[0] == '\0') {
+         strncpy(res->xoap_t, (char *)cur->children->content, 31);
+       }
+      } else if (!xmlStrcmp(cur->name, (const xmlChar *) "r")) {
+       res->bar = atoi((char *)cur->children->content);
+      } else if (!xmlStrcmp(cur->name, (const xmlChar *) "s")) {
+       res->wind_s = atoi((char *)cur->children->content);
+      } else if (!xmlStrcmp(cur->name, (const xmlChar *) "d")) {
+       if (isdigit((char)cur->children->content[0])) {
+           res->wind_d = atoi((char *)cur->children->content);
+         }
+      } else if (!xmlStrcmp(cur->name, (const xmlChar *) "hmid")) {
+       res->hmid = atoi((char *)cur->children->content);
+      }
+    }
+    parse_cc(res, cur->children);
+  }
+  return;
+}
+
+static void parse_weather_xml(PWEATHER *res, const char *data)
+{
+  xmlDocPtr doc;
+  xmlNodePtr cur;
+
+  if (!(doc = xmlReadMemory(data, strlen(data), "", NULL, 0))) {
+    ERR("weather: can't read xml data");
+    return;
+  }
+
+  cur = xmlDocGetRootElement(doc);
+
+  while(cur) {
+    if (cur->type == XML_ELEMENT_NODE) {
+      if (!xmlStrcmp(cur->name, (const xmlChar *) "weather")) {
+       cur = cur->children;
+       while (cur != NULL) {
+         if (cur->type == XML_ELEMENT_NODE) {
+           if (!xmlStrcmp(cur->name, (const xmlChar *) "cc")) {
+             parse_cc(res, cur->children);
+             xmlFreeDoc(doc);
+             return;
+           }
+         }
+         cur = cur->next;
+       }
+      }
+    }
+    cur = cur->next;
+  }
+
+  ERR("weather: incorrect xml data");
+  xmlFreeDoc(doc);
+  return ;
+}
+
 /*
  * Horrible hack to avoid using regexes
  *
@@ -337,6 +412,7 @@ static inline void parse_token(PWEATHER *res, char *token) {
 
                                //First 3 digits are wind direction
                                strncpy(s_tmp, token, 3);
+                               s_tmp[3]='\0';
                                res->wind_d=atoi(s_tmp);
 
                                //4th and 5th digit are wind speed in knots (convert to km/hr)
@@ -393,6 +469,7 @@ static inline void parse_token(PWEATHER *res, char *token) {
 
                                //First 3 digits are wind direction
                                strncpy(s_tmp, token, 3);
+                               s_tmp[3]='\0';
                                res->wind_d=atoi(s_tmp);
 
                                //4th and 5th digit are wind speed in m/s (convert to km/hr)
@@ -410,38 +487,44 @@ static inline void parse_token(PWEATHER *res, char *token) {
 
 static void parse_weather(PWEATHER *res, const char *data)
 {
-       char s_tmp[256];
-       const char delim[] = " ";
-
+        //Reset results
        memset(res, 0, sizeof(PWEATHER));
 
-       //Divide time stamp and metar data
-       if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
+       //Check if it is an xml file
+       if ( strncmp(data, "<?xml ", 6) == 0 ) {
+         parse_weather_xml(res, data);
+       } else {
+         //We assume its a text file
+         char s_tmp[256];
+         const char delim[] = " ";
 
-               //Process all tokens
-               char *p_tok = NULL;
-               char *p_save = NULL;
+         //Divide time stamp and metar data
+         if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
 
-               if ((strtok_r(s_tmp, delim, &p_save)) != NULL) {
+           //Process all tokens
+           char *p_tok = NULL;
+           char *p_save = NULL;
 
-                       //Jump first token, must be icao
-                       p_tok = strtok_r(NULL, delim, &p_save);
+           if ((strtok_r(s_tmp, delim, &p_save)) != NULL) {
 
-                       do {
+             //Jump first token, must be icao
+             p_tok = strtok_r(NULL, delim, &p_save);
 
-                               parse_token(res, p_tok);
-                               p_tok = strtok_r(NULL, delim, &p_save);
+             do {
 
-                       } while (p_tok != NULL);
-               }
-               return;
-       }
-       else {
-               return;
+               parse_token(res, p_tok);
+               p_tok = strtok_r(NULL, delim, &p_save);
+
+             } while (p_tok != NULL);
+           }
+           return;
+         }
+         else {
+           return;
+         }
        }
 }
 
-
 void fetch_weather_info(location *curloc)
 {
        CURL *curl = NULL;
@@ -468,7 +551,7 @@ void fetch_weather_info(location *curloc)
                        timed_thread_unlock(curloc->p_timed_thread);
                        free(chunk.memory);
                } else {
-                       ERR("No data from server");
+                       ERR("weather: no data from server");
                }
 
                curl_easy_cleanup(curl);
@@ -485,12 +568,12 @@ void init_thread(location *curloc, int interval)
                timed_thread_create(&weather_thread,
                                (void *)curloc, interval * 1000000);
        if (!curloc->p_timed_thread) {
-               ERR("Error creating weather timed thread");
+               ERR("weather: error creating timed thread");
        }
        timed_thread_register(curloc->p_timed_thread,
                        &curloc->p_timed_thread);
        if (timed_thread_run(curloc->p_timed_thread)) {
-               ERR("Error running weather timed thread");
+               ERR("weather: error running timed thread");
        }
 }
 
@@ -507,14 +590,15 @@ void process_weather_info(char *p, int p_max_size, char *uri, char *data_type, i
        location *curloc = find_location(uri);
        if (!curloc->p_timed_thread) init_thread(curloc, interval);
 
-
        timed_thread_lock(curloc->p_timed_thread);
        if (strcmp(data_type, "last_update") == EQUAL) {
                strncpy(p, curloc->data.lastupd, p_max_size);
        } else if (strcmp(data_type, "temperature") == EQUAL) {
                temp_print(p, p_max_size, curloc->data.temp, TEMP_CELSIUS);
        } else if (strcmp(data_type, "cloud_cover") == EQUAL) {
-               if (curloc->data.cc == 0) {
+               if (curloc->data.xoap_t[0] != '\0') {
+                       strncpy(p, curloc->data.xoap_t, p_max_size);
+               } else if (curloc->data.cc == 0) {
                        strncpy(p, "", p_max_size);
                } else if (curloc->data.cc < 3) {
                        strncpy(p, "clear", p_max_size);
@@ -575,6 +659,7 @@ void process_weather_info(char *p, int p_max_size, char *uri, char *data_type, i
        } else if (strcmp(data_type, "weather") == EQUAL) {
                strncpy(p, wc[curloc->data.wc], p_max_size);
        }
+
        timed_thread_unlock(curloc->p_timed_thread);
 }
 
index ff2d687..3c9b9d4 100644 (file)
@@ -30,7 +30,8 @@
 
 /* WEATHER data */
 typedef struct PWEATHER_ {
-  char lastupd[17];
+  char lastupd[32];
+  char xoap_t[32];
   int temp;
   int dew;
   int cc;
@@ -39,6 +40,16 @@ typedef struct PWEATHER_ {
   int wind_d;
   int hmid;
   int wc;
+  /*
+   * TODO:
+   * Is it worth investigating about using icons from weather.com?
+   * We could use them for data from noaa as well.
+   * They can display nicely with cimlib_add_image (with appropriate
+   * #ifdefs on imlib2 and x11), and an additional input argoment for position.
+
+  char icon[3];
+
+  */
 } PWEATHER;
 
 /* Prototypes */