aboutsummaryrefslogtreecommitdiffstats
path: root/binding
diff options
context:
space:
mode:
authorMatt Ranostay <matt.ranostay@konsulko.com>2018-02-02 11:19:38 -0800
committerMatt Ranostay <matt.ranostay@konsulko.com>2018-02-11 19:44:43 -0800
commitb1634e9c58a8442391aa782f967ddc3f85cd5cd9 (patch)
treecf8a9d62699a7d4b8094b2ce1e1a2560148aef79 /binding
parent3da3a30f2e2261750d4e110c9b64447192dffd23 (diff)
binding: weather: add initial openweathermap binding
Bug-AGL: SPEC-1273 Change-Id: Idf6f12148df840ad8cfe2c1bc8eb35c1bc759876 Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
Diffstat (limited to 'binding')
-rw-r--r--binding/CMakeLists.txt39
-rw-r--r--binding/afm-weather-binding.c264
2 files changed, 303 insertions, 0 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
new file mode 100644
index 0000000..bdd5d5b
--- /dev/null
+++ b/binding/CMakeLists.txt
@@ -0,0 +1,39 @@
+###########################################################################
+# Copyright 2015, 2016, 2017 IoT.bzh
+#
+# author: Fulup Ar Foll <fulup@iot.bzh>
+# contrib: Romain Forlot <romain.forlot@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+# Add target to project dependency list
+PROJECT_TARGET_ADD(afm-weather-binding)
+
+ # Define project Targets
+ add_library(afm-weather-binding MODULE afm-weather-binding.c)
+
+ # Binder exposes a unique public entry point
+ SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES
+ LABELS "BINDING"
+ LINK_FLAGS ${BINDINGS_LINK_FLAG}
+ OUTPUT_NAME ${TARGET_NAME}
+ )
+
+ # Library dependencies (include updates automatically)
+ TARGET_LINK_LIBRARIES(${TARGET_NAME} ${link_libraries})
+
+ # installation directory
+ INSTALL(TARGETS ${TARGET_NAME}
+ LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR})
+
diff --git a/binding/afm-weather-binding.c b/binding/afm-weather-binding.c
new file mode 100644
index 0000000..e2094c0
--- /dev/null
+++ b/binding/afm-weather-binding.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2018 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <curl/curl.h>
+#include <json-c/json.h>
+
+#define AFB_BINDING_VERSION 2
+#include <afb/afb-binding.h>
+
+#define OPENWEATHER_API_KEY "a860fa437924aec3d0360cc749e25f0e"
+#define OPENWEATHER_URL "http://api.openweathermap.org/data/2.5/weather?lat=%.4f&lon=%.4f&units=imperial&APPID=%s"
+
+struct {
+ char *api_key;
+ char url[128];
+ char buffer[1024];
+} data;
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static struct afb_event weather_event;
+
+static int init()
+{
+ json_object *response, *query;
+ int ret;
+
+ ret = afb_daemon_require_api("persistence", 1);
+ if (ret < 0) {
+ AFB_ERROR("Cannot request data persistence service");
+ return ret;
+ }
+
+ query = json_object_new_object();
+ json_object_object_add(query, "key", json_object_new_string("OPENWEATHERMAP_API_KEY"));
+
+ ret = afb_service_call_sync("persistence", "read", query, &response);
+
+ if (ret < 0) {
+ data.api_key = strdup(OPENWEATHER_API_KEY);
+ AFB_WARNING("Cannot get OPENWEATHERMAP_API_KEY from persistence storage, defaulting to %s", data.api_key);
+ } else {
+ json_object *jresp = NULL;
+
+ json_object_object_get_ex(response, "response", &jresp);
+ json_object_object_get_ex(jresp, "value", &jresp);
+
+ data.api_key = strdup(json_object_get_string(jresp));
+
+ AFB_NOTICE("OPENWEATHERMAP_API_KEY retrieved from persistence: %s", data.api_key);
+ }
+
+ json_object_put(response);
+
+ ret = afb_daemon_require_api("geoclue", 1);
+ if (ret < 0) {
+ AFB_ERROR("Cannot request GeoClue service");
+ return ret;
+ }
+
+ query = json_object_new_object();
+ json_object_object_add(query, "value", json_object_new_string("location"));
+
+ ret = afb_service_call_sync("geoclue", "subscribe", query, &response);
+ json_object_put(response);
+
+ if (ret < 0) {
+ AFB_ERROR("Cannot subscribe to GeoClue service");
+ return ret;
+ }
+
+ weather_event = afb_daemon_make_event("weather");
+
+ return 0;
+}
+
+static size_t weather_cb(char *ptr, size_t size, size_t nmemb, void *usr)
+{
+ json_object *jresp = NULL;
+ size_t realsize = size * nmemb;
+
+ if (realsize > (sizeof(data.buffer) - 1))
+ return -ENOMEM;
+
+ pthread_mutex_lock(&mutex);
+
+ memcpy(data.buffer, ptr, realsize);
+ data.buffer[realsize] = '\0';
+
+ jresp = json_tokener_parse(data.buffer);
+
+ if (jresp)
+ json_object_object_add(jresp, "url", json_object_new_string(data.url));
+
+ pthread_mutex_unlock(&mutex);
+ afb_event_push(weather_event, jresp);
+
+ return realsize;
+}
+
+static void onevent(const char *event, struct json_object *object)
+{
+ json_object *val = NULL;
+ CURL *ch = NULL;
+ double latitude, longitude;
+ int ret;
+
+ if (strcasecmp(event, "geoclue/location") || data.api_key == NULL)
+ return;
+
+ ret = json_object_object_get_ex(object, "latitude", &val);
+ if (!ret)
+ return;
+ latitude = json_object_get_double(val);
+
+ ret = json_object_object_get_ex(object, "longitude", &val);
+ if (!ret)
+ return;
+ longitude = json_object_get_double(val);
+
+ sprintf(data.url, OPENWEATHER_URL, latitude, longitude, data.api_key);
+
+ ch = curl_easy_init();
+ if (ch == NULL)
+ return;
+
+ curl_easy_setopt(ch, CURLOPT_URL, data.url);
+ curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, weather_cb);
+
+ (void) curl_easy_perform(ch);
+ curl_easy_cleanup(ch);
+}
+
+static void current_weather(struct afb_req request)
+{
+ json_object *jresp = NULL;
+
+ pthread_mutex_lock(&mutex);
+ jresp = json_tokener_parse(data.buffer);
+
+ if (jresp)
+ json_object_object_add(jresp, "url", json_object_new_string(data.url));
+
+ pthread_mutex_unlock(&mutex);
+
+ if (jresp == NULL) {
+ afb_req_fail(request, "failed", "No weather data currently");
+ return;
+ }
+
+ afb_req_success(request, jresp, "weather data");
+}
+
+static int validate_key(const char *value)
+{
+ int i;
+
+ for (i = 0; i < strlen(value); i++)
+ if (!isxdigit(value[i])) return -EINVAL;
+
+ return 0;
+}
+
+static void api_key(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+ json_object *jresp = NULL;
+ int ret;
+
+ if (!value) {
+ jresp = json_object_new_object();
+ json_object_object_add(jresp, "api_key", json_object_new_string(data.api_key));
+ afb_req_success(request, jresp, "application key");
+ return;
+ }
+
+ if (strlen(value) != strlen(OPENWEATHER_API_KEY) || validate_key(value)) {
+ afb_req_fail(request, "failed", "Invalid OpenWeatherMap API key");
+ return;
+ }
+
+ jresp = json_object_new_object();
+ json_object_object_add(jresp, "key", json_object_new_string("OPENWEATHERMAP_API_KEY"));
+ json_object_object_add(jresp, "value", json_object_new_string(value));
+
+ ret = afb_service_call_sync("persistence", "update", jresp, NULL);
+ if (ret < 0) {
+ afb_req_fail(request, "failed", "Couldn't store API key");
+ return;
+ }
+
+ if (data.api_key != NULL) {
+ free(data.api_key);
+ data.api_key = strdup(value);
+ }
+
+ afb_req_success(request, jresp, NULL);
+}
+
+static void subscribe(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+
+ if (value && !strcasecmp(value, "weather")) {
+ afb_req_subscribe(request, weather_event);
+ afb_req_success(request, NULL, NULL);
+ return;
+ }
+
+ afb_req_fail(request, "failed", "Invalid event");
+}
+
+static void unsubscribe(struct afb_req request)
+{
+ const char *value = afb_req_value(request, "value");
+
+ if (value && !strcasecmp(value, "weather")) {
+ afb_req_unsubscribe(request, weather_event);
+ afb_req_success(request, NULL, NULL);
+ return;
+ }
+
+ afb_req_fail(request, "failed", "Invalid event");
+}
+
+static const struct afb_verb_v2 binding_verbs[] = {
+ { .verb = "current_weather", .callback = current_weather, .info = "Current weather data" },
+ { .verb = "api_key", .callback = api_key, .info = "Get/set OpenWeatherMap API key" },
+ { .verb = "subscribe", .callback = subscribe, .info = "Subscribe to weather events" },
+ { .verb = "unsubscribe", .callback = unsubscribe, .info = "Unsubscribe to weather events" },
+ { }
+};
+
+/*
+ * binder API description
+ */
+const struct afb_binding_v2 afbBindingV2 = {
+ .api = "weather",
+ .specification = "Weather service API",
+ .verbs = binding_verbs,
+ .init = init,
+ .onevent = onevent,
+};