1774e29e92cc1758a63a3cef078d45480153f16f
[apps/agl-service-navigation.git] / binding / navigation-api.c
1 /*
2  * Copyright (C) 2019 Konsulko Group
3  * Author: Matt Ranostay <matt.ranostay@konsulko.com>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/time.h>
24 #include <time.h>
25
26 #include <glib.h>
27 #include <glib/gstdio.h>
28 #include <gio/gio.h>
29 #include <json-c/json.h>
30
31 #define AFB_BINDING_VERSION 3
32 #include <afb/afb-binding.h>
33
34 #include "navigation-api.h"
35
36 afb_api_t g_api;
37
38 static const char *vshl_capabilities_events[] = {
39         "setDestination",
40         "cancelNavigation",
41         NULL,
42 };
43
44 struct navigation_state *navigation_get_userdata(void)
45 {
46         return afb_api_get_userdata(g_api);
47 }
48
49 static afb_event_t get_event_from_value(struct navigation_state *ns,
50                         const char *value)
51 {
52         if (!g_strcmp0(value, "status"))
53                 return ns->status_event;
54
55         if (!g_strcmp0(value, "position"))
56                 return ns->position_event;
57
58         if (!g_strcmp0(value, "waypoints"))
59                 return ns->waypoints_event;
60
61         return NULL;
62 }
63
64 static json_object **get_storage_from_value(struct navigation_state *ns,
65                         const char *value)
66 {
67         if (!g_strcmp0(value, "status"))
68                 return &ns->status_storage;
69
70         if (!g_strcmp0(value, "position"))
71                 return &ns->position_storage;
72
73         if (!g_strcmp0(value, "waypoints"))
74                 return &ns->waypoints_storage;
75
76         return NULL;
77 }
78
79 static void navigation_subscribe_unsubscribe(afb_req_t request,
80                 gboolean unsub)
81 {
82         struct navigation_state *ns = navigation_get_userdata();
83         json_object *jresp = json_object_new_object();
84         const char *value;
85         afb_event_t event;
86         int rc;
87
88         value = afb_req_value(request, "value");
89         if (!value) {
90                 afb_req_fail_f(request, "failed", "Missing \"value\" event");
91                 return;
92         }
93
94         event = get_event_from_value(ns, value);
95         if (!event) {
96                 afb_req_fail_f(request, "failed", "Bad \"value\" event \"%s\"",
97                                 value);
98                 return;
99         }
100
101         if (!unsub) {
102                 json_object *storage;
103                 rc = afb_req_subscribe(request, event);
104
105                 g_rw_lock_reader_lock(&ns->rw_lock);
106                 storage = *get_storage_from_value(ns, value);
107                 if (storage) {
108                         // increment reference counter, and send out cached value
109                         json_object_get(storage);
110                         afb_event_push(event, storage);
111                 }
112                 g_rw_lock_reader_unlock(&ns->rw_lock);
113         } else {
114                 rc = afb_req_unsubscribe(request, event);
115         }
116         if (rc != 0) {
117                 afb_req_fail_f(request, "failed",
118                                         "%s error on \"value\" event \"%s\"",
119                                         !unsub ? "subscribe" : "unsubscribe",
120                                         value);
121                 return;
122         }
123
124         afb_req_success_f(request, jresp, "Navigation %s to event \"%s\"",
125                         !unsub ? "subscribed" : "unsubscribed",
126                         value);
127 }
128
129 static void subscribe(afb_req_t request)
130 {
131         navigation_subscribe_unsubscribe(request, FALSE);
132 }
133
134 static void unsubscribe(afb_req_t request)
135 {
136         navigation_subscribe_unsubscribe(request, TRUE);
137 }
138
139 static void broadcast(json_object *jresp, const char *name, gboolean cache)
140 {
141         struct navigation_state *ns = navigation_get_userdata();
142         afb_event_t event = get_event_from_value(ns, name);
143         json_object *tmp = NULL;
144
145         if (json_object_deep_copy(jresp, (json_object **) &tmp, NULL))
146                 return;
147
148         if (cache) {
149                 json_object **storage;
150
151                 g_rw_lock_writer_lock(&ns->rw_lock);
152
153                 storage = get_storage_from_value(ns, name);
154
155                 if (*storage)
156                         json_object_put(*storage);
157                 *storage = NULL;
158
159                 // increment reference for storage
160                 json_object_get(tmp);
161                 *storage = tmp;
162
163                 // increment reference for event
164                 json_object_get(tmp);
165                 afb_event_push(event, tmp);
166
167                 g_rw_lock_writer_unlock(&ns->rw_lock);
168
169                 return;
170         }
171
172         afb_event_push(event, tmp);
173 }
174
175 static void broadcast_status(afb_req_t request)
176 {
177         json_object *jresp = afb_req_json(request);
178         broadcast(jresp, "status", TRUE);
179
180         afb_req_success(request, NULL, "Broadcast status send");
181
182         // NOTE: If the Alexa SDK API for pushing local navigation
183         //       updates gets exposed, send update to vshl-capabilities
184         //       here.
185 }
186
187 static void broadcast_position(afb_req_t request)
188 {
189         const char *position = afb_req_value(request, "position");
190         gboolean cache = FALSE;
191
192         // only send out a car position event on subscribe
193         if (position && !g_strcmp0(position, "car"))
194                 cache = TRUE;
195
196         json_object *jresp = afb_req_json(request);
197         broadcast(jresp, "position", cache);
198
199         afb_req_success(request, NULL, "Broadcast position send");
200
201         // NOTE: If the Alexa SDK API for pushing local navigation
202         //       updates gets exposed, send update to vshl-capabilities
203         //       here.
204 }
205
206 static void broadcast_waypoints(afb_req_t request)
207 {
208         json_object *jresp = afb_req_json(request);
209         broadcast(jresp, "waypoints", TRUE);
210
211         afb_req_success(request, NULL, "Broadcast waypoints send");
212
213         // NOTE: If the Alexa SDK API for pushing local navigation
214         //       updates gets exposed, send update to vshl-capabilities
215         //       here.
216 }
217
218 static void handle_setDestination_event(struct json_object *object)
219 {
220         json_object *jdest = NULL;
221         json_object_object_get_ex(object, "destination", &jdest);
222         if(!jdest) {
223                 AFB_WARNING("setDestination event missing destination element");
224                 return;
225         }
226
227         json_object *jcoord = NULL;
228         json_object_object_get_ex(jdest, "coordinate", &jcoord);
229         if(!jcoord) {
230                 AFB_WARNING("setDestination event missing coordinate element");
231                 return;
232         }
233
234         json_object *jlat = NULL;
235         json_object_object_get_ex(jcoord, "latitudeInDegrees", &jlat);
236         if(!jlat)
237                 return;
238         errno = 0;
239         double lat = json_object_get_double(jlat);
240         if(errno != 0)
241                 return;
242
243         json_object *jlon = NULL;
244         json_object_object_get_ex(jcoord, "longitudeInDegrees", &jlon);
245         if(!jlon)
246                 return;
247         double lon = json_object_get_double(jlon);
248         if(errno != 0)
249                 return;
250
251         json_object *jobj = json_object_new_object();
252         json_object *jpoints = json_object_new_array();
253         json_object *jpoint = json_object_new_object();
254         jlat = json_object_new_double(lat);
255         jlon = json_object_new_double(lon);
256         json_object_object_add(jpoint, "latitude", jlat);
257         json_object_object_add(jpoint, "longitude", jlon);
258         json_object_array_add(jpoints, jpoint);
259         json_object_object_add(jobj, "points", jpoints);
260         broadcast(jobj, "waypoints", TRUE);
261 }
262
263 static void handle_cancelNavigation_event(struct json_object *object)
264 {
265         json_object *jobj = json_object_new_object();
266         json_object *jstate = json_object_new_string("stop");
267         json_object_object_add(jobj, "state", jstate);
268         broadcast(jobj, "status", TRUE);
269 }
270
271 static void onevent(afb_api_t api, const char *event, struct json_object *object)
272 {
273         if(!event)
274                 return;
275
276         if(strcmp(event, "vshl-capabilities/setDestination") == 0) {
277                 handle_setDestination_event(object);
278         } else if(strcmp(event, "vshl-capabilities/cancelNavigation") == 0) {
279                 handle_cancelNavigation_event(object);
280         } else {
281                 AFB_WARNING("Unhandled vshl-capabilities event");
282         }
283 }
284
285 static int init(afb_api_t api)
286 {
287         struct navigation_state *ns;
288         int rc;
289
290         ns = g_try_malloc0(sizeof(*ns));
291         if (!ns) {
292                 AFB_ERROR("out of memory allocating navigation state");
293                 return -ENOMEM;
294         }
295
296         rc = afb_daemon_require_api("vshl-capabilities", 1);
297         if (!rc) {
298                 const char **tmp = vshl_capabilities_events;
299                 json_object *args = json_object_new_object();
300                 json_object *actions = json_object_new_array();
301
302                 while (*tmp) {
303                         json_object_array_add(actions, json_object_new_string(*tmp++));
304                 }
305                 json_object_object_add(args, "actions", actions);
306                 if(json_object_array_length(actions)) {
307                         rc = afb_api_call_sync(api, "vshl-capabilities", "navigation/subscribe",
308                                                args, NULL, NULL, NULL);
309                         if(rc != 0)
310                                 AFB_WARNING("afb_api_call_sync returned %d", rc);
311                 } else {
312                         json_object_put(args);
313                 }
314         } else {
315                 AFB_WARNING("unable to initialize vshl-capabilities binding");
316         }
317
318         ns->status_event = afb_daemon_make_event("status");
319         ns->position_event = afb_daemon_make_event("position");
320         ns->waypoints_event = afb_daemon_make_event("waypoints");
321
322         if (!afb_event_is_valid(ns->status_event) ||
323             !afb_event_is_valid(ns->position_event) ||
324             !afb_event_is_valid(ns->waypoints_event)) {
325                 AFB_ERROR("Cannot create events");
326                 return -EINVAL;
327         }
328
329         afb_api_set_userdata(api, ns);
330         g_api = api;
331
332         g_rw_lock_init(&ns->rw_lock);
333
334         return 0;
335 }
336
337 static const afb_verb_t binding_verbs[] = {
338         {
339                 .verb = "subscribe",
340                 .callback = subscribe,
341                 .info = "Subscribe to event"
342         }, {
343                 .verb = "unsubscribe",
344                 .callback = unsubscribe,
345                 .info = "Unsubscribe to event"
346         }, {
347                 .verb = "broadcast_status",
348                 .callback = broadcast_status,
349                 .info = "Allows clients to broadcast status events"
350         }, {
351                 .verb = "broadcast_position",
352                 .callback = broadcast_position,
353                 .info = "Broadcast out position event"
354         }, {
355                 .verb = "broadcast_waypoints",
356                 .callback = broadcast_waypoints,
357                 .info = "Broadcast out waypoint event"
358         },
359         {}
360 };
361
362 /*
363  * description of the binding for afb-daemon
364  */
365 const afb_binding_t afbBindingV3 = {
366         .api = "navigation",
367         .verbs = binding_verbs,
368         .onevent = onevent,
369         .init = init,
370 };