/* * Copyright (C) 2016 "IoT.bzh" * Author José Bollo * * 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 #include #include #include #include #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(); }