summaryrefslogtreecommitdiffstats
path: root/meta-app-framework/recipes-example/afm-client/afm-client_1.0.bb
blob: 4cd80db64f9784098acd1ba8cc5db6329505e2e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
SUMMARY = "Sample client for AFM to install/start/stop/remove applications"
DESCRIPTION = "afm-client is a sample AngularJS/HTML5 application using \
Application Framework Manager to install, start, stop, or remove \
applications provided as .wgt widget packages."
HOMEPAGE = "http://www.iot.bzh"

inherit systemd

LICENSE = "GPLv3+"
LIC_FILES_CHKSUM = "file://LICENSE;md5=6cb04bdb88e11107e3af4d8e3f301be5"

#DEPENDS = "nodejs-native"
RDEPENDS_${PN} = "af-main af-binder af-main-binding af-binder-binding-demopost af-binder-binding-authlogin"

SRC_URI_git = "git://gerrit.automotivelinux.org/gerrit/src/app-framework-demo;protocol=https;branch=master"
SRC_URI_files = "file://afm-client \
           file://afm-client.service \
          "
SRC_URI = "${SRC_URI_git} \
           ${SRC_URI_files} \
          "
SRCREV = "9e9b459fa27d7a359a060024c9639b99b45813d5"
S = "${WORKDIR}/git/afm-client"

do_install () {
  mkdir -p ${D}/${datadir}/agl/afm-client
  cp -ra ${S}/dist.prod/* ${D}/${datadir}/agl/afm-client/

  mkdir -p ${D}/${bindir}
  install -m 0755 ${WORKDIR}/afm-client ${D}/${bindir}/afm-client

  if ${@bb.utils.contains('DISTRO_FEATURES', 'systemd', 'true', 'false', d)}; then
    install -d ${D}${systemd_user_unitdir}
    install -d ${D}${sysconfdir}/systemd/user/default.target.wants
    install -m 0644 ${WORKDIR}/afm-client.service ${D}/${systemd_user_unitdir}/afm-client.service
    ln -sf ${systemd_user_unitdir}/afm-client.service ${D}${sysconfdir}/systemd/user/default.target.wants
  fi
}

FILES_${PN} += "${datadir} ${systemd_user_unitdir}"
r.Long */ }
/*
 * Copyright (C) 2017 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <gps.h>
#include <time.h>
#include <pthread.h>
#include <json-c/json.h>

#define AFB_BINDING_VERSION 2
#include <afb/afb-binding.h>

static struct gps_data_t data;
static struct afb_event location_event;

static pthread_t thread;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

#define MSECS_TO_USECS(x) (x * 1000)

struct {
	FILE *current_file;
	char buffer[32];
	int count;
} recording;

// struct dop_t item order
static const char *dop_names[] = {
	"xdop",
	"ydop",
	"pdop",
	"hdop",
	"vdop",
	"tdop",
	"gdop",
	NULL
};

static json_object *populate_json_dop_data(json_object *jresp, struct dop_t *dop)
{
	char **names = (char **) dop_names;
	double *tmp = (double *) dop;
	json_object *value = NULL;

	while (*names) {
		double val = *tmp++;

		if (val != 0) {
			value = json_object_new_double(val);
			json_object_object_add(jresp, *names, value);
		}
		names++;
	}

	return jresp;
}

static json_object *populate_json_data(json_object *jresp)
{
	json_object *value = NULL;

	if (data.fix.mode != MODE_3D) {
		json_object_put(jresp);
		return NULL;
	}

	if (data.set & ALTITUDE_SET) {
		value = json_object_new_double(data.fix.altitude);
		json_object_object_add(jresp, "altitude", value);
	}

	if (data.set & LATLON_SET) {
		value = json_object_new_double(data.fix.latitude);
		json_object_object_add(jresp, "latitude", value);

		value = json_object_new_double(data.fix.longitude);
		json_object_object_add(jresp, "longitude", value);
	}

	if (data.set & SPEED_SET) {
		value = json_object_new_double(data.fix.speed);
		json_object_object_add(jresp, "speed", value);
	}

	if (data.set & TRACK_SET) {
		value = json_object_new_double(data.fix.track);
		json_object_object_add(jresp, "track", value);
	}

	if (data.set & TIME_SET) {
		char time[30];
		unix_to_iso8601(data.fix.time, (char *) &time, sizeof(time));
		value = json_object_new_string(time);
		json_object_object_add(jresp, "timestamp", value);
	}

	jresp = populate_json_dop_data(jresp, &data.dop);

	return jresp;
}

static void get_data(struct afb_req request)
{
	json_object *jresp = NULL;
	pthread_mutex_lock(&mutex);

	jresp = populate_json_data(json_object_new_object());
	if (jresp != NULL) {
		afb_req_success(request, jresp, "GNSS location data");
	} else {
		afb_req_fail(request, "failed", "No 3D GNSS fix");
	}

	pthread_mutex_unlock(&mutex);
}

static json_object *gps_recording_state(json_object *jresp)
{
	json_object *value = NULL;

	if (recording.current_file == NULL) {
		value = json_object_new_boolean(true);
		json_object_object_add(jresp, "recording", value);
	} else {
		value = json_object_new_boolean(true);
		json_object_object_add(jresp, "recording", value);

		value = json_object_new_string(recording.buffer);
		json_object_object_add(jresp, "filename", value);

		if (recording.count) {
			value = json_object_new_int(recording.count);
			json_object_object_add(jresp, "count", value);
		}
	}

	return jresp;
}

static void record(struct afb_req request)
{
	json_object *jresp = NULL;
	const char *value = afb_req_value(request, "state");
	bool record = false;

	if (!value) {
		pthread_mutex_lock(&mutex);
		jresp = gps_recording_state(json_object_new_object());
		pthread_mutex_unlock(&mutex);

		afb_req_success(request, jresp, "GPS Current Recording State");
		return;
	}

	if (!strcasecmp(value, "on"))
		record = true;
	else if (!strcasecmp(value, "off"))
		record  = false;
	else {
		afb_req_fail(request, "failed", "Invalid state requested");
		return;
	}

	pthread_mutex_lock(&mutex);

	if (recording.current_file != NULL) {
		recording.count = 0;
		fclose(recording.current_file);
		recording.current_file = NULL;

		if (!record) {
			pthread_mutex_unlock(&mutex);
			afb_req_success(request, NULL, NULL);
			return;
		}
	} else if (recording.current_file == NULL && !record) {
		pthread_mutex_unlock(&mutex);
		afb_req_fail(request, "failed", "Recording was already stopped");
		return;
	}

	{
		time_t cur_time;
		struct tm *info;

		time(&cur_time);
		info = localtime(&cur_time);
		strftime((char *) &recording.buffer, sizeof(recording.buffer),
						 "gps_%Y%m%d_%H%M.log", info);

		recording.current_file = fopen(recording.buffer, "w+");

		jresp = gps_recording_state(json_object_new_object());
		afb_req_success(request, jresp, "GPS Recording Result");
	}

	pthread_mutex_unlock(&mutex);
}


static void subscribe(struct afb_req request)
{
	const char *value = afb_req_value(request, "value");

	if (value && !strcasecmp(value, "location")) {
		afb_req_subscribe(request, location_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, "location")) {
		afb_req_unsubscribe(request, location_event);
		afb_req_success(request, NULL, NULL);
		return;
	}

	afb_req_fail(request, "failed", "Invalid event");
}

static void add_record(json_object *jresp)
{
	fprintf(recording.current_file, "%s\n", json_object_to_json_string(jresp));
	fflush(recording.current_file);

	recording.count++;
}

static void *data_poll(void *ptr)
{
	/*
	 * keep reading till an error condition happens
	 */
	while (gps_waiting(&data, MSECS_TO_USECS(2000)) && !errno)
	{
		json_object *jresp = NULL;

		pthread_mutex_lock(&mutex);
		if (gps_read(&data) == -1) {
			AFB_ERROR("Cannot read from GPS daemon.\n");
			pthread_mutex_unlock(&mutex);
			break;
		}

		if (!(data.set & (TRACKERR_SET | SPEEDERR_SET| CLIMBERR_SET))) {
			jresp = populate_json_data(json_object_new_object());

			if (recording.current_file != NULL)
				add_record(jresp);

			if (jresp != NULL)
				afb_event_push(location_event, jresp);
		}
		pthread_mutex_unlock(&mutex);
	}

	AFB_INFO("Closing GPS daemon connection.\n");
	gps_stream(&data, WATCH_DISABLE, NULL);
	gps_close(&data);

	_exit(0);

	return NULL;
}

static int init()
{
	const char *host, *port;
	int ret, tries = 5;

	location_event = afb_daemon_make_event("location");

	host = getenv("AFBGPS_HOST") ? : "localhost";
	port = getenv("AFBGPS_SERVICE") ? : "2947";

	ret = gps_open(host, port, &data);
	if (ret < 0)
		return ret;

	gps_stream(&data, WATCH_ENABLE | WATCH_JSON, NULL);

	// due to the gpsd.socket race condition need to loop till initial event
	do {
		gps_read(&data);
	} while (!gps_waiting(&data, MSECS_TO_USECS(2500)) && tries--);

	return pthread_create(&thread, NULL, &data_poll, NULL);
}

static const struct afb_verb_v2 binding_verbs[] = {
	{ .verb = "location",    .callback = get_data,     .info = "Get GNSS data" },
	{ .verb = "record",	 .callback = record,       .info = "Record GPS data" },
	{ .verb = "subscribe",   .callback = subscribe,    .info = "Subscribe to GNSS events" },
	{ .verb = "unsubscribe", .callback = unsubscribe,  .info = "Unsubscribe to GNSS events" },
	{ }
};

/*
 * binder API description
 */
const struct afb_binding_v2 afbBindingV2 = {
	.api = "gps",
	.specification = "GNSS/GPS API",
	.verbs = binding_verbs,
	.init = init,
};