/* * Copyright (C) 2019 Konsulko Group * Author: Matt Ranostay * * 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 #include #include #include #include #include #include #include #include #include #define AFB_BINDING_VERSION 3 #include #include "navigation-api.h" afb_api_t g_api; static const char *vshl_capabilities_events[] = { "setDestination", "cancelNavigation", NULL, }; struct navigation_state *navigation_get_userdata(void) { return afb_api_get_userdata(g_api); } static afb_event_t get_event_from_value(struct navigation_state *ns, const char *value) { if (!g_strcmp0(value, "status")) return ns->status_event; if (!g_strcmp0(value, "position")) return ns->position_event; if (!g_strcmp0(value, "waypoints")) return ns->waypoints_event; return NULL; } static json_object **get_storage_from_value(struct navigation_state *ns, const char *value) { if (!g_strcmp0(value, "status")) return &ns->status_storage; if (!g_strcmp0(value, "position")) return &ns->position_storage; if (!g_strcmp0(value, "waypoints")) return &ns->waypoints_storage; return NULL; } static void navigation_subscribe_unsubscribe(afb_req_t request, gboolean unsub) { struct navigation_state *ns = navigation_get_userdata(); json_object *jresp = json_object_new_object(); const char *value; afb_event_t event; int rc; value = afb_req_value(request, "value"); if (!value) { afb_req_fail_f(request, "failed", "Missing \"value\" event"); return; } event = get_event_from_value(ns, value); if (!event) { afb_req_fail_f(request, "failed", "Bad \"value\" event \"%s\"", value); return; } if (!unsub) { json_object *storage; rc = afb_req_subscribe(request, event); g_rw_lock_reader_lock(&ns->rw_lock); storage = *get_storage_from_value(ns, value); if (storage) { // increment reference counter, and send out cached value json_object_get(storage); afb_event_push(event, storage); } g_rw_lock_reader_unlock(&ns->rw_lock); } else { rc = afb_req_unsubscribe(request, event); } if (rc != 0) { afb_req_fail_f(request, "failed", "%s error on \"value\" event \"%s\"", !unsub ? "subscribe" : "unsubscribe", value); return; } afb_req_success_f(request, jresp, "Navigation %s to event \"%s\"", !unsub ? "subscribed" : "unsubscribed", value); } static void subscribe(afb_req_t request) { navigation_subscribe_unsubscribe(request, FALSE); } static void unsubscribe(afb_req_t request) { navigation_subscribe_unsubscribe(request, TRUE); } static void broadcast(json_object *jresp, const char *name, gboolean cache) { struct navigation_state *ns = navigation_get_userdata(); afb_event_t event = get_event_from_value(ns, name); json_object *tmp = NULL; if (json_object_deep_copy(jresp, (json_object **) &tmp, NULL)) return; if (cache) { json_object **storage; g_rw_lock_writer_lock(&ns->rw_lock); storage = get_storage_from_value(ns, name); if (*storage) json_object_put(*storage); *storage = NULL; // increment reference for storage json_object_get(tmp); *storage = tmp; // increment reference for event json_object_get(tmp); afb_event_push(event, tmp); g_rw_lock_writer_unlock(&ns->rw_lock); return; } afb_event_push(event, tmp); } static void broadcast_status(afb_req_t request) { json_object *jresp = afb_req_json(request); broadcast(jresp, "status", TRUE); afb_req_success(request, NULL, "Broadcast status send"); // NOTE: If the Alexa SDK API for pushing local navigation // updates gets exposed, send update to vshl-capabilities // here. } static void broadcast_position(afb_req_t request) { const char *position = afb_req_value(request, "position"); gboolean cache = FALSE; // only send out a car position event on subscribe if (position && !g_strcmp0(position, "car")) cache = TRUE; json_object *jresp = afb_req_json(request); broadcast(jresp, "position", cache); afb_req_success(request, NULL, "Broadcast position send"); // NOTE: If the Alexa SDK API for pushing local navigation // updates gets exposed, send update to vshl-capabilities // here. } static void broadcast_waypoints(afb_req_t request) { json_object *jresp = afb_req_json(request); broadcast(jresp, "waypoints", TRUE); afb_req_success(request, NULL, "Broadcast waypoints send"); // NOTE: If the Alexa SDK API for pushing local navigation // updates gets exposed, send update to vshl-capabilities // here. } static void handle_setDestination_event(struct json_object *object) { json_object *jdest = NULL; json_object_object_get_ex(object, "destination", &jdest); if(!jdest) { AFB_WARNING("setDestination event missing destination element"); return; } json_object *jcoord = NULL; json_object_object_get_ex(jdest, "coordinate", &jcoord); if(!jcoord) { AFB_WARNING("setDestination event missing coordinate element"); return; } json_object *jlat = NULL; json_object_object_get_ex(jcoord, "latitudeInDegrees", &jlat); if(!jlat) return; errno = 0; double lat = json_object_get_double(jlat); if(errno != 0) return; json_object *jlon = NULL; json_object_object_get_ex(jcoord, "longitudeInDegrees", &jlon); if(!jlon) return; double lon = json_object_get_double(jlon); if(errno != 0) return; json_object *jobj = json_object_new_object(); json_object *jpoints = json_object_new_array(); json_object *jpoint = json_object_new_object(); jlat = json_object_new_double(lat); jlon = json_object_new_double(lon); json_object_object_add(jpoint, "latitude", jlat); json_object_object_add(jpoint, "longitude", jlon); json_object_array_add(jpoints, jpoint); json_object_object_add(jobj, "points", jpoints); broadcast(jobj, "waypoints", TRUE); } static void handle_cancelNavigation_event(struct json_object *object) { json_object *jobj = json_object_new_object(); json_object *jstate = json_object_new_string("stop"); json_object_object_add(jobj, "state", jstate); broadcast(jobj, "status", TRUE); } static void onevent(afb_api_t api, const char *event, struct json_object *object) { if(!event) return; if(strcmp(event, "vshl-capabilities/setDestination") == 0) { handle_setDestination_event(object); } else if(strcmp(event, "vshl-capabilities/cancelNavigation") == 0) { handle_cancelNavigation_event(object); } else { AFB_WARNING("Unhandled vshl-capabilities event"); } } static int init(afb_api_t api) { struct navigation_state *ns; int rc; ns = g_try_malloc0(sizeof(*ns)); if (!ns) { AFB_ERROR("out of memory allocating navigation state"); return -ENOMEM; } rc = afb_daemon_require_api("vshl-capabilities", 1); if (!rc) { const char **tmp = vshl_capabilities_events; json_object *args = json_object_new_object(); json_object *actions = json_object_new_array(); while (*tmp) { json_object_array_add(actions, json_object_new_string(*tmp++)); } json_object_object_add(args, "actions", actions); if(json_object_array_length(actions)) { rc = afb_api_call_sync(api, "vshl-capabilities", "navigation/subscribe", args, NULL, NULL, NULL); if(rc != 0) AFB_WARNING("afb_api_call_sync returned %d", rc); } else { json_object_put(args); } } else { AFB_WARNING("unable to initialize vshl-capabilities binding"); } ns->status_event = afb_daemon_make_event("status"); ns->position_event = afb_daemon_make_event("position"); ns->waypoints_event = afb_daemon_make_event("waypoints"); if (!afb_event_is_valid(ns->status_event) || !afb_event_is_valid(ns->position_event) || !afb_event_is_valid(ns->waypoints_event)) { AFB_ERROR("Cannot create events"); return -EINVAL; } afb_api_set_userdata(api, ns); g_api = api; g_rw_lock_init(&ns->rw_lock); return 0; } static const afb_verb_t binding_verbs[] = { { .verb = "subscribe", .callback = subscribe, .info = "Subscribe to event" }, { .verb = "unsubscribe", .callback = unsubscribe, .info = "Unsubscribe to event" }, { .verb = "broadcast_status", .callback = broadcast_status, .info = "Allows clients to broadcast status events" }, { .verb = "broadcast_position", .callback = broadcast_position, .info = "Broadcast out position event" }, { .verb = "broadcast_waypoints", .callback = broadcast_waypoints, .info = "Broadcast out waypoint event" }, {} }; /* * description of the binding for afb-daemon */ const afb_binding_t afbBindingV3 = { .api = "navigation", .verbs = binding_verbs, .onevent = onevent, .init = init, };