aboutsummaryrefslogtreecommitdiffstats
path: root/binding
diff options
context:
space:
mode:
Diffstat (limited to 'binding')
-rw-r--r--binding/CMakeLists.txt39
-rw-r--r--binding/afm-gps-binding.c1051
2 files changed, 1090 insertions, 0 deletions
diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt
new file mode 100644
index 0000000..ceb48d9
--- /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-gps-binding)
+
+ # Define project Targets
+ add_library(afm-gps-binding MODULE afm-gps-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-gps-binding.c b/binding/afm-gps-binding.c
new file mode 100644
index 0000000..080f1e5
--- /dev/null
+++ b/binding/afm-gps-binding.c
@@ -0,0 +1,1051 @@
+/*
+ * Copyright (C) 2016 "IoT.bzh"
+ * Author José Bollo <jose.bollo@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.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <json-c/json.h>
+
+#include <systemd/sd-event.h>
+
+#include <afb/afb-binding.h>
+#include <afb/afb-service-itf.h>
+
+#define NAUTICAL_MILE_IN_METER 1852
+#define MILE_IN_METER 1609.344
+#define KNOT_TO_METER_PER_SECOND 0.5144444444 /* 1852 / 3600 */
+#define METER_PER_SECOND_TO_KNOT 1.943844492 /* 3600 / 1852 */
+#define METER_PER_SECOND_TO_KILOMETER_PER_HOUR 3.6 /* 3600 / 1000 */
+#define METER_PER_SECOND_TO_MILE_PER_HOUR 2.236936292 /* 3600 / 1609.344 */
+
+#define DEFAULT_PERIOD 2000 /* 2 seconds */
+
+/*
+ * references:
+ *
+ * https://www.w3.org/TR/geolocation-API/
+ * http://www.gpsinformation.org/dale/nmea.htm
+ */
+
+/* flags for recording what field is set */
+struct flags {
+ unsigned time: 1;
+ unsigned latitude: 1;
+ unsigned longitude: 1;
+ unsigned altitude: 1;
+ unsigned speed: 1;
+ unsigned track: 1;
+};
+
+/* the gps data converted */
+struct gps {
+ struct flags set;
+
+ uint32_t time;
+ double latitude;
+ double longitude;
+ double altitude;
+ double speed;
+ double track;
+};
+
+/*
+ * the type of position expected
+ *
+ * here, this type is mainly the selection of units
+ */
+enum type {
+ type_wgs84, /* longitude, latitude, track: degre, altitude: m, speed: m/s */
+ type_dms_kmh, /* longitude, latitude: degre°minute'second.xxx"X, track: degre, altitude: m, speed: km/h */
+ type_dms_mph, /* longitude, latitude: degre°minute'second.xxx"X, track: degre, altitude: m, speed: mph */
+ type_dms_kn, /* longitude, latitude: degre°minute'second.xxx"X, track: degre, altitude: m, speed: kn */
+ type_COUNT,
+ type_DEFAULT = type_wgs84,
+ type_INVALID = -1
+};
+
+struct event;
+
+/*
+ * for each expected period
+ */
+struct period {
+ struct period *next; /* link to the next other period */
+ struct event *events; /* events for the period */
+ uint32_t period; /* value of the period in ms */
+ uint32_t last; /* last update of the period */
+};
+
+/*
+ * each generated event
+ */
+struct event {
+ struct event *next; /* link for the same period */
+ const char *name; /* name of the event */
+ struct afb_event event; /* the event for the binder */
+ enum type type; /* the type of data expected */
+ int id; /* id of the event for unsubscribe */
+};
+
+/*
+ * names of the types
+ */
+static const char * const type_NAMES[type_COUNT] = {
+ "WGS84",
+ "DMS.km/h",
+ "DMS.mph",
+ "DMS.kn"
+};
+
+/*
+ * the interface to afb-daemon
+ */
+const struct afb_binding_interface *afbitf;
+
+/*
+ * records the raw frames
+ */
+static struct gps frames[10]; /* a short memory for further computation if needed */
+static int frameidx; /* index of the last frame (frames are in the reverse order) */
+static int newframes; /* boolean indication of wether new frames are availables */
+
+/*
+ * records the JSON object for sending positions
+ */
+static struct json_object *time_ms; /* time as double in millisecond */
+static struct json_object *latitude_wgs; /* latitude as double in degree */
+static struct json_object *longitude_wgs; /* longitude as double in degree */
+static struct json_object *latitude_dms; /* latitude as string in d°m's.s"X */
+static struct json_object *longitude_dms; /* longitude as string in d°m's.s"X */
+static struct json_object *altitude_m; /* altitude as double in meter */
+static struct json_object *speed_ms; /* speed as double in m/s */
+static struct json_object *speed_kmh; /* speed as double in km/h */
+static struct json_object *speed_mph; /* speed as double in mph */
+static struct json_object *speed_kn; /* speed as double in kn */
+static struct json_object *track_d; /* heading track as double in degree */
+
+static struct json_object *positions[type_COUNT]; /* computed positions by type */
+
+/* head of the list of periods */
+static struct period *list_of_periods;
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: FORMATING JSON POSITIONS **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+/*
+ * Creates the JSON representation for Degree Minute Second representation of coordinates
+ */
+static struct json_object *new_dms(double a, int islat)
+{
+ char buffer[50], pos;
+ double D, M;
+
+ if (islat) {
+ if (a >= 0)
+ pos = 'N';
+ else {
+ a = -a;
+ pos = 'S';
+ }
+ } else {
+ if (a <= 180)
+ pos = 'E';
+ else {
+ a = 360 - a;
+ pos = 'W';
+ }
+ }
+ D = floor(a);
+ a = (a - D) * 60;
+ M = floor(a);
+ a = (a - M) * 60;
+ sprintf(buffer, "%d°%d'%.3f\"%c", (int)D, (int)M, a, pos);
+ return json_object_new_string(buffer);
+}
+
+/*
+ * adds the value (with reference count increment) if not null
+ */
+static void addif(struct json_object *obj, const char *name, struct json_object *val)
+{
+ if (val != NULL)
+ json_object_object_add(obj, name, json_object_get(val));
+}
+
+/*
+ * release the object (put) and reset the pointer to null
+ */
+static void clear(struct json_object **obj)
+{
+ json_object_put(*obj);
+ *obj = NULL;
+}
+
+/*
+ * get the last/current position of type
+ */
+static struct json_object *position(enum type type)
+{
+ struct json_object *result;
+ struct gps *g0;
+
+ /* clean on new frame */
+ if (newframes) {
+ clear(&time_ms);
+ clear(&latitude_wgs);
+ clear(&longitude_wgs);
+ clear(&latitude_dms);
+ clear(&longitude_dms);
+ clear(&altitude_m);
+ clear(&speed_ms);
+ clear(&speed_kmh);
+ clear(&speed_mph);
+ clear(&speed_kn);
+ clear(&track_d);
+ clear(&positions[type_wgs84]);
+ clear(&positions[type_dms_kmh]);
+ clear(&positions[type_dms_mph]);
+ clear(&positions[type_dms_kn]);
+ newframes = 0;
+ }
+
+ /* get the result */
+ result = positions[type];
+ if (result == NULL) {
+ DEBUG(afbitf, "building position for type %s", type_NAMES[type]);
+
+ /* should build the result */
+ g0 = &frames[frameidx];
+ result = json_object_new_object();
+ if (result == NULL)
+ return NULL;
+ positions[type] = result;
+
+ /* set the result type */
+ json_object_object_add(result, "type", json_object_new_string(type_NAMES[type]));
+
+ /* build time, altitude and track */
+ if (time_ms == NULL && g0->set.time)
+ time_ms = json_object_new_double (g0->time);
+ addif(result, "time", time_ms);
+ if (altitude_m == NULL && g0->set.altitude)
+ altitude_m = json_object_new_double (g0->altitude);
+ addif(result, "altitude", altitude_m);
+ if (track_d == NULL && g0->set.track)
+ track_d = json_object_new_double (g0->track);
+ addif(result, "track", track_d);
+
+ /* build position */
+ switch (type) {
+ default:
+ case type_wgs84:
+ if (latitude_wgs == NULL && g0->set.latitude)
+ latitude_wgs = json_object_new_double (g0->latitude);
+ addif(result, "latitude", latitude_wgs);
+ if (longitude_wgs == NULL && g0->set.longitude)
+ longitude_wgs = json_object_new_double (g0->longitude);
+ addif(result, "longitude", longitude_wgs);
+ break;
+ case type_dms_kmh:
+ case type_dms_mph:
+ case type_dms_kn:
+ if (latitude_dms == NULL && g0->set.latitude)
+ latitude_dms = new_dms (g0->latitude, 1);
+ addif(result, "latitude", latitude_dms);
+ if (longitude_dms == NULL && g0->set.longitude)
+ longitude_dms = new_dms (g0->longitude, 0);
+ addif(result, "longitude", longitude_dms);
+ break;
+ }
+
+ /* build speed */
+ switch (type) {
+ default:
+ case type_wgs84:
+ if (speed_ms == NULL && g0->set.speed)
+ speed_ms = json_object_new_double (g0->speed);
+ addif(result, "speed", speed_ms);
+ break;
+ case type_dms_kmh:
+ if (speed_kmh == NULL && g0->set.speed)
+ speed_kmh = json_object_new_double (g0->speed * METER_PER_SECOND_TO_KILOMETER_PER_HOUR);
+ addif(result, "speed", speed_kmh);
+ break;
+ case type_dms_mph:
+ if (speed_mph == NULL && g0->set.speed)
+ speed_mph = json_object_new_double (g0->speed * METER_PER_SECOND_TO_MILE_PER_HOUR);
+ addif(result, "speed", speed_mph);
+ break;
+ case type_dms_kn:
+ if (speed_kn == NULL && g0->set.speed)
+ speed_kn = json_object_new_double (g0->speed * METER_PER_SECOND_TO_KNOT);
+ addif(result, "speed", speed_kn);
+ break;
+ }
+ }
+
+ return json_object_get(result);
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: MANAGING EVENTS **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+/*
+ * get the event handler of given id
+ */
+static struct event *event_of_id(int id)
+{
+ struct period *p;
+ struct event *e;
+
+ p = list_of_periods;
+ while(p != NULL) {
+ e = p->events;
+ p = p->next;
+ while(e != NULL) {
+ if (e->id == id)
+ return e;
+ e = e->next;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * get the event handler for the type and the period
+ */
+static struct event *event_get(enum type type, int period)
+{
+ static int id;
+ int shift;
+ uint32_t perio;
+ struct period *p, **pp, *np;
+ struct event *e;
+
+ /* normalize the period */
+ period = period <= 100 ? 1 : period > 60000 ? 600 : (period / 100);
+ shift = 0;
+ while((period >> shift) > 31)
+ shift++;
+ perio = (uint32_t)(100 * (((period >> shift) & 31) << shift));
+
+ /* search for the period */
+ pp = &list_of_periods;
+ p = *pp;
+ while(p != NULL && p->period < perio) {
+ pp = &p->next;
+ p = *pp;
+ }
+
+ /* create the period if it misses */
+ if (p == NULL || p->period != perio) {
+ np = calloc(1, sizeof *p);
+ if (np == NULL)
+ return NULL;
+ np->next = p;
+ np->period = perio;
+ *pp = np;
+ p = np;
+ }
+
+ /* search the type */
+ e = p->events;
+ while(e != NULL && e->type != type)
+ e = e->next;
+
+ /* creates the type if needed */
+ if (e == NULL) {
+ e = calloc(1, sizeof *e);
+ if (e == NULL)
+ return NULL;
+
+ e->name = "GPS"; /* TODO */
+ e->event = afb_daemon_make_event(afbitf->daemon, e->name);
+ if (e->event.itf == NULL) {
+ free(e);
+ return NULL;
+ }
+
+ e->next = p->events;
+ e->type = type;
+ do {
+ id++;
+ if (id < 0)
+ id = 1;
+ } while(event_of_id(id) != NULL);
+ e->id = id;
+ p->events = e;
+ }
+
+ return e;
+}
+
+/*
+ * Sends the events if needed
+ */
+static void event_send()
+{
+ struct period *p, **pp;
+ struct event *e, **pe;
+ struct timeval tv;
+ uint32_t now;
+
+ /* skip if nothing is new */
+ if (!newframes)
+ return;
+
+ /* computes now */
+ gettimeofday(&tv, NULL);
+ now = (uint32_t)(tv.tv_sec * 1000) + (uint32_t)(tv.tv_usec / 1000);
+
+ /* iterates over the periods */
+ pp = &list_of_periods;
+ p = *pp;
+ while (p != NULL) {
+ if (p->events == NULL) {
+ /* no event for the period, frees it */
+ *pp = p->next;
+ free(p);
+ } else {
+ if (p->period <= now - p->last) {
+ /* its time to refresh */
+ p->last = now;
+ pe = &p->events;
+ e = *pe;
+ while (e != NULL) {
+ /* sends the event */
+ if (afb_event_push(e->event, position(e->type)) != 0)
+ pe = &e->next;
+ else {
+ /* no more listeners, free the event */
+ *pe = e->next;
+ afb_event_drop(e->event);
+ free(e);
+ }
+ e = *pe;
+ }
+ }
+ pp = &p->next;
+ }
+ p = *pp;
+ }
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: HANDLING NMEA **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+/*
+ * interprets a nmea time
+ */
+static int nmea_time(const char *text, uint32_t *result)
+{
+ uint32_t x;
+
+ if (text[0] < '0' || text[0] > '2'
+ || text[1] < '0' || text[1] > (text[0] == '2' ? '3' : '9')
+ || text[2] < '0' || text[2] > '5'
+ || text[3] < '0' || text[3] > '9'
+ || text[4] < '0' || text[4] > '5'
+ || text[5] < '0' || text[5] > '9'
+ || (text[6] != 0 && text[6] != '.'))
+ return 0;
+
+ x = (uint32_t)(text[0] - '0');
+ x = x * 10 + (uint32_t)(text[1]-'0');
+ x = x * 6 + (uint32_t)(text[2]-'0');
+ x = x * 10 + (uint32_t)(text[3]-'0');
+ x = x * 6 + (uint32_t)(text[4]-'0');
+ x = x * 10 + (uint32_t)(text[5]-'0');
+ x = x * 1000;
+ if (text[6] == '.') {
+ if (text[7] != 0) {
+ if (text[7] < '0' || text[7] > '9') return 0;
+ x += (uint32_t)(text[7]-'0') * 100;
+ if (text[8] != 0) {
+ if (text[8] < '0' || text[8] > '9') return 0;
+ x += (uint32_t)(text[8]-'0') * 10;
+ if (text[9] != 0) {
+ if (text[9] < '0' || text[9] > '9') return 0;
+ x += (uint32_t)(text[9]-'0');
+ if (text[10] != 0) {
+ if (text[10] < '0' || text[10] > '9') return 0;
+ x += text[10] > '5';
+ }
+ }
+ }
+ }
+ }
+
+ *result = x;
+ return 1;
+}
+
+/*
+ * interprets a nmea angle having minutes
+ */
+static int nmea_angle(const char *text, double *result)
+{
+ uint32_t x = 0;
+ double v;
+ int dotidx = (int)(strchrnul(text, '.') - text);
+
+ switch(dotidx) {
+ case 5:
+ if (text[dotidx - 5] < '0' || text[dotidx - 5] > '9')
+ return 0;
+ x = x * 10 + (uint32_t)(text[dotidx - 5] - '0');
+ case 4:
+ if (text[dotidx - 4] < '0' || text[dotidx - 4] > '9')
+ return 0;
+ x = x * 10 + (uint32_t)(text[dotidx - 4] - '0');
+ case 3:
+ if (text[dotidx - 3] < '0' || text[dotidx - 3] > '9')
+ return 0;
+ x = x * 10 + (uint32_t)(text[dotidx - 3] - '0');
+ case 2:
+ v = atof(&text[dotidx - 2]);
+ break;
+ case 1:
+ if (text[dotidx - 1] < '0' || text[dotidx - 1] > '9')
+ return 0;
+ case 0:
+ v = atof(text);
+ break;
+ default:
+ return 0;
+ }
+
+ *result = (double)x + v * 0.01666666666666666666666; /* 1 / 60 */
+
+ return 1;
+}
+
+/*
+ * creates a new position for the given optionnal fields
+ * returns 1 if correct or 0 if a format error exists
+ */
+static int nmea_set(
+ const char *tim,
+ const char *lat, const char *latu,
+ const char *lon, const char *lonu,
+ const char *alt, const char *altu,
+ const char *spe,
+ const char *tra,
+ const char *dat
+)
+{
+ struct gps gps;
+
+ DEBUG(afbitf, "time=%s latitude=%s%s longitude=%s%s altitude=%s%s speed=%s track=%s date=%s",
+ tim, lat, latu, lon, lonu, alt, altu, spe, tra, dat);
+
+ /* get the time in milliseconds */
+ if (tim == NULL)
+ gps.set.time = 0;
+ else {
+ if (!nmea_time(tim, &gps.time))
+ return 0;
+ gps.set.time = 1;
+ }
+
+ /* get the latitude */
+ if (lat == NULL || latu == NULL)
+ gps.set.latitude = 0;
+ else {
+ if ((latu[0] != 'N' && latu[0] != 'S') || latu[1] != 0)
+ return 0;
+ if (!nmea_angle(lat, &gps.latitude))
+ return 0;
+ if (latu[0] == 'S')
+ gps.latitude = -gps.latitude;
+ gps.set.latitude = 1;
+ }
+
+ /* get the longitude */
+ if (lon == NULL || lonu == NULL)
+ gps.set.longitude = 0;
+ else {
+ if ((lonu[0] != 'E' && lonu[0] != 'W') || lonu[1] != 0)
+ return 0;
+ if (!nmea_angle(lon, &gps.longitude))
+ return 0;
+ if (lonu[0] == 'W')
+ gps.longitude = 360.0 - gps.longitude;
+ gps.set.longitude = 1;
+ }
+
+ /* get the altitude */
+ if (alt == NULL || altu == NULL)
+ gps.set.altitude = 0;
+ else {
+ if (altu[0] != 'M' || altu[1] != 0)
+ return 0;
+ gps.altitude = atof(alt);
+ gps.set.altitude = 1;
+ }
+
+ /* get the speed */
+ if (spe == NULL)
+ gps.set.speed = 0;
+ else {
+ gps.speed = atof(spe) * KNOT_TO_METER_PER_SECOND;
+ gps.set.speed = 1;
+ }
+
+ /* get the track */
+ if (tra != NULL)
+ gps.set.track = 0;
+ else {
+ gps.track = atof(tra);
+ gps.set.track = 1;
+ }
+
+ /* push the frame */
+ frameidx = (frameidx ? : (int)(sizeof frames / sizeof *frames)) - 1;
+ frames[frameidx] = gps;
+ newframes++;
+
+ DEBUG(afbitf, "time:%d=%d latitude:%d=%g longitude:%d=%g altitude:%d=%g speed:%d=%g track:%d=%g",
+ (int)gps.set.time, gps.set.time ? (int)gps.time : 0,
+ (int)gps.set.latitude, gps.set.latitude ? gps.latitude : 0,
+ (int)gps.set.longitude, gps.set.longitude ? gps.longitude : 0,
+ (int)gps.set.altitude, gps.set.altitude ? gps.altitude : 0,
+ (int)gps.set.speed, gps.set.speed ? gps.speed : 0,
+ (int)gps.set.track, gps.set.track ? gps.track : 0
+ );
+
+ return 1;
+}
+
+/*
+ * Splits the nmea sentences in its fields
+ */
+static int nmea_split(char *s, char *fields[], int count)
+{
+ int index = 0;
+ while (*s && index < count) {
+ fields[index++] = s;
+ while (*s && *s != ',')
+ s++;
+ if (*s == ',')
+ *s++ = 0;
+ }
+ return !*s && index == count;
+}
+
+/*
+ * interprete one sentence GGA - Fix information
+ */
+static int nmea_gga(char *s)
+{
+ char *f[14];
+
+ return nmea_split(s, f, (int)(sizeof f / sizeof *f))
+ && *f[5] != '0'
+ && nmea_set(f[0], f[1], f[2], f[3], f[4], f[6], f[7], NULL, NULL, NULL);
+}
+
+/*
+ * interprete one sentence RMC - Recommended Minimum
+ */
+static int nmea_rmc(char *s)
+{
+ char *f[12];
+
+ return nmea_split(s, f, (int)(sizeof f / sizeof *f))
+ && *f[1] == 'A'
+ && nmea_set(f[0], f[2], f[3], f[4], f[5], NULL, NULL, f[6], f[7], f[8]);
+}
+
+
+/*
+ * interprete one NMEA sentence
+ */
+static int nmea_sentence(char *s)
+{
+ if (!s[0] || !s[1])
+ return 0;
+
+ if (s[2] == 'G' && s[3] == 'G' && s[4] == 'A' && s[5] == ',')
+ return nmea_gga(&s[6]);
+
+ if (s[2] == 'R' && s[3] == 'M' && s[4] == 'C' && s[5] == ',')
+ return nmea_rmc(&s[6]);
+
+ return 0;
+}
+
+/*
+ * reads the NMEA stream
+ */
+static int nmea_read(int fd)
+{
+ static char buffer[160];
+ static int pos = 0;
+ static int overflow = 0;
+
+ int rc;
+
+ for(;;) {
+ rc = (int)read(fd, &buffer[pos], sizeof buffer - (size_t)pos);
+ if (rc < 0) {
+ /* its an error if not interrupted */
+ if (errno != EINTR)
+ return rc;
+ } else if (rc == 0) {
+ /* nothing more to be read */
+ return 0;
+ } else {
+ /* scan the buffer */
+ while (pos != rc) {
+ if (buffer[pos] != '\n') {
+ pos++;
+ if (pos == rc) {
+ if (pos == (int)(sizeof buffer)) {
+ overflow = 1;
+ pos = 0;
+ rc = 0;
+ }
+ }
+ } else {
+ if (buffer[0] == '$' && pos > 0 && buffer[pos-1] == '\r' && !overflow) {
+ if (pos > 3 && buffer[pos-4] == '*') {
+ /* TODO: check the cheksum */
+ buffer[pos-4] = 0;
+ } else {
+ buffer[pos-1] = 0;
+ }
+ nmea_sentence(&buffer[1]);
+ }
+ pos++;
+ rc -= pos;
+ if (rc > 0)
+ memmove(buffer, buffer+pos, (size_t)rc);
+ pos = 0;
+ overflow = 0;
+ }
+ }
+ }
+ }
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: HANDLING OF CONNECTION **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+/* declare the connection routine */
+static int connection();
+
+/*
+ * called on an event on the NMEA stream
+ */
+static int on_event(sd_event_source *s, int fd, uint32_t revents, void *userdata)
+{
+ /* read available data */
+ if ((revents & EPOLLIN) != 0) {
+ nmea_read(fd);
+ event_send();
+ }
+
+ /* check if error or hangup */
+ if ((revents & (EPOLLERR|EPOLLRDHUP|EPOLLHUP)) != 0) {
+ sd_event_source_unref(s);
+ close(fd);
+ connection(fd);
+ }
+
+ return 0;
+}
+
+/*
+ * opens a socket to a host and a service (or port)
+ */
+static int open_socket_to(const char *host, const char *service)
+{
+ int rc, fd;
+ struct addrinfo hint, *rai, *iai;
+
+ /* get addr */
+ memset(&hint, 0, sizeof hint);
+ hint.ai_family = AF_INET;
+ hint.ai_socktype = SOCK_STREAM;
+ rc = getaddrinfo(host, service, &hint, &rai);
+ if (rc != 0)
+ return -1;
+
+ /* get the socket */
+ iai = rai;
+ while (iai != NULL) {
+ fd = socket(iai->ai_family, iai->ai_socktype, iai->ai_protocol);
+ if (fd >= 0) {
+ rc = connect(fd, iai->ai_addr, iai->ai_addrlen);
+ if (rc == 0) {
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ freeaddrinfo(rai);
+ return fd;
+ }
+ close(fd);
+ }
+ iai = iai->ai_next;
+ }
+ freeaddrinfo(rai);
+ return -1;
+}
+
+/*
+ * connection to nmea stream for the host and the port
+ */
+static int connect_to(const char *host, const char *service, int isgpsd)
+{
+ sd_event_source *source;
+ int rc, fd;
+
+ /* TODO connect to somewhere else */
+ fd = open_socket_to(host, service);
+ if (fd < 0) {
+ ERROR(afbitf, "can't connect to host %s, service %s", host, service);
+ return fd;
+ }
+ if (isgpsd) {
+ static const char gpsdsetup[] = "?WATCH={\"enable\":true,\"nmea\":true};\r\n";
+ write(fd, gpsdsetup, sizeof gpsdsetup - 1);
+ }
+
+ /* adds to the event loop */
+ rc = sd_event_add_io(afb_daemon_get_event_loop(afbitf->daemon), &source, fd, EPOLLIN, on_event, NULL);
+ if (rc < 0) {
+ close(fd);
+ ERROR(afbitf, "can't coonect host %s, service %s to the event loop", host, service);
+ } else {
+ NOTICE(afbitf, "Connected to host %s, service %s", host, service);
+ }
+ return rc;
+}
+
+/*
+ * connection to nmea stream
+ */
+static int connection()
+{
+ const char *host;
+ const char *service;
+ int isgpsd;
+
+ /* TODO connect to somewhere else */
+ host = getenv("AFBGPS_HOST") ? : "sinagot.net";
+ service = getenv("AFBGPS_SERVICE") ? : "5001";
+ isgpsd = getenv("AFBGPS_ISNMEA") ? 0 : 1;
+ return connect_to(host, service, isgpsd);
+}
+
+/***************************************************************************************/
+/***************************************************************************************/
+/** **/
+/** **/
+/** SECTION: BINDING VERBS IMPLEMENTATION **/
+/** **/
+/** **/
+/***************************************************************************************/
+/***************************************************************************************/
+/*
+ * Returns the type corresponding to the given name
+ */
+static enum type type_of_name(const char *name)
+{
+ enum type result;
+ if (name == NULL)
+ return type_DEFAULT;
+ for (result = 0 ; result != type_COUNT ; result++)
+ if (strcmp(type_NAMES[result], name) == 0)
+ return result;
+ return type_INVALID;
+}
+
+/*
+ * extract a valid type from the request
+ */
+static int get_type_for_req(struct afb_req req, enum type *type)
+{
+ if ((*type = type_of_name(afb_req_value(req, "type"))) != type_INVALID)
+ return 1;
+ afb_req_fail(req, "unknown-type", NULL);
+ return 0;
+}
+
+/*
+ * Get the last known position
+ *
+ * parameter of the get are:
+ *
+ * type: string: the type of position expected (defaults to "WGS84" if not present)
+ *
+ * returns the position
+ *
+ * The valid types are:
+ *
+ * +==========+=======================+=======+==========+=======+
+ * | type | longitude & latitude | speed | altitude | track |
+ * +==========+=======================+=======+==========+=======+
+ * | WGS84 | degre | m/s | | |
+ * +----------+-----------------------+-------+ | |
+ * | DMS.km/h | | km/h | | |
+ * +----------+ +-------+ meter | degre |
+ * | DMS.mph | deg°min'sec"X | mph | | |
+ * +----------+ +-------+ | |
+ * | DMS.kn | | kn | | |
+ * +==========+=======================+=======+==========+=======+
+ */
+static void get(struct afb_req req)
+{
+ enum type type;
+ if (get_type_for_req(req, &type))
+ afb_req_success(req, position(type), NULL);
+}
+
+/*
+ * subscribe to notification of position
+ *
+ * parameters of the subscription are:
+ *
+ * type: string: the type of position expected (defaults to WCS84 if not present)
+ * see the list above (get)
+ * period: integer: the expected period in milliseconds (defaults to 2000 if not present)
+ *
+ * returns an object with 2 fields:
+ *
+ * name: string: the name of the event without its prefix
+ * id: integer: a numeric identifier of the event to be used for unsubscribing
+ */
+static void subscribe(struct afb_req req)
+{
+ enum type type;
+ const char *period;
+ struct event *event;
+ struct json_object *json;
+
+ if (get_type_for_req(req, &type)) {
+ period = afb_req_value(req, "period");
+ event = event_get(type, period == NULL ? DEFAULT_PERIOD : atoi(period));
+ if (event == NULL)
+ afb_req_fail(req, "out-of-memory", NULL);
+ else if (afb_req_subscribe(req, event->event) != 0)
+ afb_req_fail_f(req, "failed", "afb_req_subscribe returned an error: %m");
+ else {
+ json = json_object_new_object();
+ json_object_object_add(json, "name", json_object_new_string(event->name));
+ json_object_object_add(json, "id", json_object_new_int(event->id));
+ afb_req_success(req, json, NULL);
+ }
+ }
+}
+
+/*
+ * unsubscribe a previous subscription
+ *
+ * parameters of the unsubscription are:
+ *
+ * id: integer: the numeric identifier of the event as returned when subscribing
+ */
+static void unsubscribe(struct afb_req req)
+{
+ const char *id;
+ struct event *event;
+
+ id = afb_req_value(req, "id");
+ if (id == NULL)
+ afb_req_fail(req, "missing-id", NULL);
+ else {
+ event = event_of_id(atoi(id));
+ if (event == NULL)
+ afb_req_fail(req, "bad-id", NULL);
+ else {
+ afb_req_unsubscribe(req, event->event);
+ afb_req_success(req, NULL, NULL);
+ }
+ }
+}
+
+/*
+ * array of the verbs exported to afb-daemon
+ */
+static const struct afb_verb_desc_v1 binding_verbs[] = {
+ /* VERB'S NAME SESSION MANAGEMENT FUNCTION TO CALL SHORT DESCRIPTION */
+ { .name= "get", .session= AFB_SESSION_NONE, .callback= get, .info= "get the last known data" },
+ { .name= "subscribe", .session= AFB_SESSION_NONE, .callback= subscribe, .info= "subscribe to notification of position" },
+ { .name= "unsubscribe", .session= AFB_SESSION_NONE, .callback= unsubscribe, .info= "unsubscribe a previous subscription" },
+ { .name= NULL } /* marker for end of the array */
+};
+
+/*
+ * description of the binding for afb-daemon
+ */
+static const struct afb_binding binding_description =
+{
+ /* description conforms to VERSION 1 */
+ .type= AFB_BINDING_VERSION_1,
+ .v1= { /* fills the v1 field of the union when AFB_BINDING_VERSION_1 */
+ .prefix= "gps", /* the API name (or binding name or prefix) */
+ .info= "Access to the GPS data", /* short description of of the binding */
+ .verbs = binding_verbs /* the array describing the verbs of the API */
+ }
+};
+
+/*
+ * activation function for registering the binding called by afb-daemon
+ */
+const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf)
+{
+ afbitf = itf; /* records the interface for accessing afb-daemon */
+ return &binding_description; /* returns the description of the binding */
+}
+
+int afbBindingV1ServiceInit(struct afb_service service)
+{
+ return connection();
+}