summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErich Eckner <git@eckner.net>2021-03-04 15:28:36 +0100
committerErich Eckner <git@eckner.net>2021-03-04 15:28:36 +0100
commit0a55b4ed86a5d9f58b200b6671aa712003a0a93c (patch)
tree9c8f8a7738dd2d4f27db7a46d65959a6ad6a09db
parent2ac4843a974822a0b547b52a274d13ac39374b9e (diff)
downloadanzeige-0a55b4ed86a5d9f58b200b6671aa712003a0a93c.tar.xz
input_gadgets.c: api.met.no: von Version 1.9 zu 2.0 aktualisiert - jetzt als json statt xml
-rw-r--r--Makefile4
-rw-r--r--input_gadgets.c251
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 <curl/curl.h>
+#include <errno.h>
+#include <json-c/json.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
+#include <locale.h>
#include <math.h>
#include <netdb.h>
#include <regex.h>
@@ -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 <weatherdata>\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 <product>\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<json_object_array_length(props); i++) {
+ data_point = json_object_array_get_idx(props, i);
+ if (!data_point) {
+ fprintf(stderr, "Failed to retrieve index %d from 'timeseries:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (!json_object_object_get_ex(data_point, "time", &datum)) {
+ fprintf(stderr, "'timeseries:[%d]' has no 'time:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (strcmp(time_str,json_object_get_string(datum)) == 0) {
+ for (int j=0; (j<15) && (i+j<json_object_array_length(props)); j++) {
+ data_point = json_object_array_get_idx(props, i+j);
+ if (!data_point) {
+ fprintf(stderr, "Failed to retrieve index %d from 'timeseries:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (!json_object_object_get_ex(data_point, "data", &data_point)) {
+ fprintf(stderr, "'timeseries:[%d]' has no 'data:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (!json_object_object_get_ex(data_point, "instant", &datum)) {
+ fprintf(stderr, "'timeseries:[%d] data:' has no 'instant:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (!json_object_object_get_ex(datum, "details", &datum)) {
+ fprintf(stderr, "'timeseries:[%d] data: instant:' has no 'details:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (!json_object_object_get_ex(datum, "air_temperature", &details)) {
+ fprintf(stderr, "'timeseries:[%d] data: instant: details:' has no 'air_temperature:'\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ cur_val = json_object_get_double(details);
+ if (errno) {
+ fprintf(stderr, "'timeseries:[%d] data: instant: details: air_temperature:' is no double\n", i);
+ json_object_put(doc);
+ return NULL;
+ }
+ if (isnan(temp_max) || (cur_val > 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;
}