From 0a55b4ed86a5d9f58b200b6671aa712003a0a93c Mon Sep 17 00:00:00 2001 From: Erich Eckner Date: Thu, 4 Mar 2021 15:28:36 +0100 Subject: input_gadgets.c: api.met.no: von Version 1.9 zu 2.0 aktualisiert - jetzt als json statt xml --- Makefile | 4 +- input_gadgets.c | 251 +++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 170 insertions(+), 85 deletions(-) diff --git a/Makefile b/Makefile index 8a71475..3b97623 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CFLAGS=-Werror -O3 -pedantic -g anzeige: anzeige.c fonts.o humidity.o input_gadgets.o multiplexer.o - gcc -o "$@" $(CFLAGS) -lpthread -lzip -lxml2 -lcurl $^ + gcc -o "$@" $(CFLAGS) -lpthread -lzip -lxml2 -ljson-c -lcurl $^ fonts.o: fonts.c fonts.h gcc -o "$@" $(CFLAGS) -c "$<" @@ -10,7 +10,7 @@ humidity.o: humidity.c humidity.h gcc -o "$@" $(CFLAGS) -c "$<" input_gadgets.o: input_gadgets.c input_gadgets.h - gcc -o "$@" $(CFLAGS) -c -I/usr/include/libxml2 -lzip -lxml2 -lcurl "$<" + gcc -o "$@" $(CFLAGS) -c -I/usr/include/libxml2 -I/usr/include/json-c -lzip -lxml2 -ljson-c -lcurl "$<" multiplexer.o: multiplexer.c multiplexer.h gcc -o "$@" $(CFLAGS) -c -lpthread "$<" diff --git a/input_gadgets.c b/input_gadgets.c index 6c3544d..ccfcc87 100644 --- a/input_gadgets.c +++ b/input_gadgets.c @@ -1,8 +1,11 @@ #define _GNU_SOURCE #include +#include +#include #include #include +#include #include #include #include @@ -314,118 +317,193 @@ double xml_extract_float(xmlNode *node, char *key) return res; } -char *gadgets_retrieve_weather_forecast(char *output, int max_len) +size_t lookForExpirationHeader(char *buffer, size_t size, size_t nitems, time_t *userdata) { + if (nitems * size <= 9) + return nitems * size; + if (memcmp(buffer, "Expires: ", 9) != 0) + return nitems * size; + buffer += 9; + struct tm temp_data; + memset(&temp_data, 0, sizeof(temp_data)); + temp_data.tm_isdst = -1; + strptime(buffer, "%a, %d %b %Y %H:%M:%S %Z", &temp_data); + *userdata = mktime(&temp_data); + + memset(&temp_data, 0, sizeof(temp_data)); + temp_data.tm_year = 70; + temp_data.tm_mday = 1; + temp_data.tm_isdst = -1; + *userdata -= mktime(&temp_data); + + return nitems * size; +} + +char *gadgets_retrieve_weather_forecast(MemoryStruct *chunk, time_t *expiration, char *output, int max_len) { - CURL *curl_handle; - CURLcode res; - MemoryStruct chunk; - int ret_val; + if (*expiration < time(NULL)) { + // cached version expired, we get a new one + chunk->size = 0; - chunk.memory = malloc(1); - chunk.size = 0; + CURL *curl_handle; + CURLcode res; + int ret_val; - curl_global_init(CURL_GLOBAL_ALL); + curl_global_init(CURL_GLOBAL_ALL); - curl_handle = curl_easy_init(); - if (!curl_handle) { - perror("Failed to init curl"); - free(chunk.memory); - return NULL; - } - curl_easy_setopt(curl_handle, CURLOPT_URL, "https://api.met.no/weatherapi/locationforecast/1.9/?lat=50.9336&lon=11.5621&msl=170"); - curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); - res = curl_easy_perform(curl_handle); - curl_easy_cleanup(curl_handle); - if (res != CURLE_OK) { - fprintf( - stderr, - "curl_easy_perform(%s) failed: %s\n", - "https://api.met.no/weatherapi/locationforecast/1.9/?lat=50.9336&lon=11.5621&msl=170", - curl_easy_strerror(res) - ); - free(chunk.memory); - if (res != CURLE_COULDNT_RESOLVE_HOST && res != CURLE_COULDNT_CONNECT && res != CURLE_PEER_FAILED_VERIFICATION) + curl_handle = curl_easy_init(); + if (!curl_handle) { + perror("Failed to init curl"); return NULL; - char *ende = output; - if (max_len > 0) - ende += snprintf(ende, max_len, "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", 0xEE, 0x01, 0xEE, 0x02, 0xEE, 0x01, 0xEE, 0x02, 0xEE, 0x05, 0xEE, 0x06, 0xEE, 0x05, 0xEE, 0x06); - return ende; + } + curl_easy_setopt(curl_handle, CURLOPT_URL, "https://api.met.no/weatherapi/locationforecast/2.0/?lat=50.9336&lon=11.5621"); + curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, chunk); + curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, lookForExpirationHeader); + curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, expiration); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + res = curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + if (res != CURLE_OK) { + fprintf( + stderr, + "curl_easy_perform(%s) failed: %s\n", + "https://api.met.no/weatherapi/locationforecast/1.9/?lat=50.9336&lon=11.5621&msl=170", + curl_easy_strerror(res) + ); + if (res != CURLE_COULDNT_RESOLVE_HOST && res != CURLE_COULDNT_CONNECT && res != CURLE_PEER_FAILED_VERIFICATION) + return NULL; + char *ende = output; + if (max_len > 0) + ende += snprintf(ende, max_len, "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", 0xEE, 0x01, 0xEE, 0x02, 0xEE, 0x01, 0xEE, 0x02, 0xEE, 0x05, 0xEE, 0x06, 0xEE, 0x05, 0xEE, 0x06); + return ende; + } } - LIBXML_TEST_VERSION + struct json_object *doc; - xmlDocPtr doc; - doc = xmlReadMemory(chunk.memory, chunk.size, "noname.xml", NULL, 0); - free(chunk.memory); + doc = json_tokener_parse(chunk->memory); if (doc == NULL) { - fprintf(stderr, "Failed to parse document\n"); + fprintf(stderr, "Failed to parse json\n"); return NULL; } - xmlNode *cur_node = NULL; - xmlNode *sub_node = NULL; - cur_node = xmlDocGetRootElement(doc); - if (!cur_node) { - fprintf(stderr, "Failed to reach root node of xml\n"); - xmlFreeDoc(doc); - return NULL; - } - cur_node = xml_find_node_by_name(cur_node, "weatherdata"); - if (!cur_node) { - fprintf(stderr, "Failed to enter \n"); - xmlFreeDoc(doc); + struct json_object *props; + + if (!json_object_object_get_ex(doc, "properties", &props)) { + fprintf(stderr, "No 'properties:' at the root.\n"); + json_object_put(doc); return NULL; } - cur_node = xml_find_node_by_name(cur_node -> children, "product"); - if (!cur_node) { - fprintf(stderr, "Failed to enter \n"); - xmlFreeDoc(doc); + + if (!json_object_object_get_ex(props, "timeseries", &props)) { + fprintf(stderr, "'properties:' has no 'timeseries:'\n"); + json_object_put(doc); return NULL; } float rain_total = 0, temp_min = NAN, temp_max = NAN, wind_max = NAN, cur_val; - char time_str[2][21]; + char time_str[21]; time_t now; time(&now); now = ((int)(now/60/60))*60*60; + strftime(time_str,21,"%Y-%m-%dT%H:%M:%SZ",gmtime(&now)); + + struct json_object *data_point; + struct json_object *datum; + struct json_object *details; + + for (int i=0; i temp_max)) + temp_max = cur_val; + if (isnan(temp_min) || (cur_val < temp_min)) + temp_min = cur_val; + if (!json_object_object_get_ex(datum, "wind_speed", &details)) { + fprintf(stderr, "'timeseries:[%d] data: instant: details:' has no 'wind_speed:'\n", i); + json_object_put(doc); + return NULL; + } + cur_val = json_object_get_double(details); + if (errno) { + fprintf(stderr, "'timeseries:[%d] data: instant: details: wind_speed:' is no double\n", i); + json_object_put(doc); + return NULL; + } + if (isnan(wind_max) || (cur_val > wind_max)) + wind_max = cur_val; - for (cur_node = cur_node -> children; cur_node; cur_node = cur_node->next) { - if ((cur_node->type == XML_ELEMENT_NODE) && (strcmp((char *)cur_node->name,"time")==0) && (xml_strcmp_property_value(cur_node, "datatype", "forecast")==0)) { - for (int i=0; i<=14; i++) { - now += i*60*60; - strftime(time_str[0],21,"%Y-%m-%dT%H:%M:%SZ",gmtime(&now)); - now += 60*60; - strftime(time_str[1],21,"%Y-%m-%dT%H:%M:%SZ",gmtime(&now)); - now -= (i+1)*60*60; - if ((xml_strcmp_property_value(cur_node, "from", time_str[1])==0) && (xml_strcmp_property_value(cur_node, "to", time_str[1])==0)) { - sub_node = xml_find_node_by_name(cur_node -> children,"location"); - cur_val = xml_extract_float(xml_find_node_by_name(sub_node -> children, "temperature"), "value"); - if (isnan(temp_max) || (cur_val > temp_max)) - temp_max = cur_val; - if (isnan(temp_min) || (cur_val < temp_min)) - temp_min = cur_val; - cur_val = xml_extract_float(xml_find_node_by_name(sub_node -> children, "windSpeed"), "mps"); - if (isnan(wind_max) || (cur_val > wind_max)) - wind_max = cur_val; + if (!json_object_object_get_ex(data_point, "next_1_hours", &datum)) { + fprintf(stderr, "'timeseries:[%d] data:' has no 'next_1_hours:'\n", i); + json_object_put(doc); + return NULL; } - if ((xml_strcmp_property_value(cur_node, "from", time_str[0])==0) && (xml_strcmp_property_value(cur_node, "to", time_str[1])==0)) { - sub_node = xml_find_node_by_name(cur_node -> children,"location"); - if (!sub_node) - continue; - sub_node = xml_find_node_by_name(sub_node -> children, "precipitation"); - if (!sub_node) - continue; - rain_total += xml_extract_float(sub_node, "value"); + if (!json_object_object_get_ex(datum, "details", &datum)) { + fprintf(stderr, "'timeseries:[%d] data: next_1_hours:' has no 'details:'\n", i); + json_object_put(doc); + return NULL; + } + if (!json_object_object_get_ex(datum, "precipitation_amount", &datum)) { + fprintf(stderr, "'timeseries:[%d] data: next_1_hours: details:' has no 'precipitation_amount:'\n", i); + json_object_put(doc); + return NULL; + } + rain_total += json_object_get_double(datum); + if (errno) { + fprintf(stderr, "'timeseries:[%d] data: next_1_hours: details: precipitation_amount:' is no double\n", i); + json_object_put(doc); + return NULL; } } } } - xmlFreeDoc(doc); + json_object_put(doc); if (max_len > 0) { int i = snprintf(output, max_len, "%0.1f ,, %0.1f °C; %0.1f mm; %0.1f m/s", temp_min, temp_max, rain_total, wind_max); @@ -447,6 +525,10 @@ void *gadgets_watch_weather_forecast(void *param) t_input_thread *p = param; time_t next_update = 0; time_t next_dns_query = 0; + MemoryStruct chunk; + chunk.memory = malloc(1); + chunk.size = 0; + time_t expiration = 0; p->text_output = malloc(MAX_TEXT_OUTPUT_LEN); if (p->text_output == NULL) { fprintf(stderr, "malloc failed\n"); @@ -459,6 +541,8 @@ void *gadgets_watch_weather_forecast(void *param) next_dns_query = time(NULL) + 30; // 1/2 minute ahead memset(p->text_output, 0, MAX_TEXT_OUTPUT_LEN); if (gadgets_retrieve_weather_forecast( + &chunk, + &expiration, p->text_output, MAX_TEXT_OUTPUT_LEN-1 )) { @@ -477,6 +561,7 @@ void *gadgets_watch_weather_forecast(void *param) usleep(100000); } free(p->text_output); + free(chunk.memory); return NULL; } -- cgit v1.2.3-54-g00ecf