osd-contiki/examples/ipv6/json-ws/json-ws.c
Joakim Eriksson 51b73127e9 * Simple JSON parser and generator.
* Simple HTTP webservice with support for both receiving and sending HTTP requests.
* json-ws example that optionally push sensor data to COSM over IPv6.
2012-06-12 16:42:16 +02:00

521 lines
16 KiB
C

/*
* Copyright (c) 2011-2012, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Contiki operating system.
*/
/**
* \file
* JSON webservice util
* \author
* Niclas Finne <nfi@sics.se>
* Joakim Eriksson <joakime@sics.se>
* Joel Hoglund <joel@sics.se>
*/
#include "contiki.h"
#if PLATFORM_HAS_LEDS
#include "dev/leds.h"
#endif
#include "httpd-ws.h"
#include "jsontree.h"
#include "jsonparse.h"
#include "json-ws.h"
#include <stdio.h>
#include <string.h>
#define DEBUG 0
#if DEBUG
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...)
#endif
#ifdef JSON_WS_CONF_CALLBACK_PROTO
#define CALLBACK_PROTO JSON_WS_CONF_CALLBACK_PROTO
#else
#define CALLBACK_PROTO "http"
#endif /* JSON_WS_CONF_CALLBACK_PROTO */
#ifdef JSON_WS_CONF_CALLBACK_PORT
#define CALLBACK_PORT JSON_WS_CONF_CALLBACK_PORT
#else
#define CALLBACK_PORT 8080;
#endif /* JSON_WS_CONF_CALLBACK_PORT */
/* Predefined startup-send interval */
#ifdef JSON_WS_CONF_CALLBACK_INTERVAL
#define SEND_INTERVAL JSON_WS_CONF_CALLBACK_INTERVAL
#else
#define SEND_INTERVAL 120
#endif
static const char http_content_type_json[] = "application/json";
/* Maximum 40 chars in host name?: 5 x 8 */
static char callback_host[40] = "[aaaa::1]";
static uint16_t callback_port = CALLBACK_PORT;
static uint16_t callback_interval = SEND_INTERVAL;
static char callback_path[80] = "/debug/";
static char callback_appdata[80] = "";
static char callback_proto[8] = CALLBACK_PROTO;
static const char *callback_json_path = NULL;
static struct jsontree_object *tree;
static struct ctimer periodic_timer;
long json_time_offset = 0;
/* support for submitting to cosm */
#if WITH_COSM
extern struct jsontree_callback cosm_value_callback;
JSONTREE_OBJECT_EXT(cosm_tree,
JSONTREE_PAIR("current_value", &cosm_value_callback));
#endif /* WITH_COSM */
static void periodic(void *ptr);
/*---------------------------------------------------------------------------*/
static void
json_copy_string(struct jsonparse_state *parser, char *string, int len)
{
jsonparse_next(parser);
jsonparse_next(parser);
jsonparse_copy_value(parser, string, len);
}
/*---------------------------------------------------------------------------*/
static int
cfg_get(struct jsontree_context *js_ctx)
{
const char *path = jsontree_path_name(js_ctx, js_ctx->depth - 1);
if(strncmp(path, "host", 4) == 0) {
jsontree_write_string(js_ctx, callback_host);
} else if(strncmp(path, "port", 4) == 0) {
jsontree_write_int(js_ctx, callback_port);
} else if(strncmp(path, "interval", 8) == 0) {
jsontree_write_int(js_ctx, callback_interval);
} else if(strncmp(path, "path", 4) == 0) {
jsontree_write_string(js_ctx, callback_path);
} else if(strncmp(path, "appdata", 7) == 0) {
jsontree_write_string(js_ctx, callback_appdata[0] == '\0' ? "" : "***");
} else if(strncmp(path, "proto", 5) == 0) {
jsontree_write_string(js_ctx, callback_proto);
}
return 0;
}
static int
cfg_set(struct jsontree_context *js_ctx, struct jsonparse_state *parser)
{
int type;
int update = 0;
while((type = jsonparse_next(parser)) != 0) {
if(type == JSON_TYPE_PAIR_NAME) {
if(jsonparse_strcmp_value(parser, "host") == 0) {
json_copy_string(parser, callback_host, sizeof(callback_host));
update++;
} else if(jsonparse_strcmp_value(parser, "path") == 0) {
json_copy_string(parser, callback_path, sizeof(callback_path));
update++;
} else if(jsonparse_strcmp_value(parser, "appdata") == 0) {
json_copy_string(parser, callback_appdata, sizeof(callback_appdata));
update++;
} else if(jsonparse_strcmp_value(parser, "proto") == 0) {
json_copy_string(parser, callback_proto, sizeof(callback_proto));
update++;
} else if(jsonparse_strcmp_value(parser, "port") == 0) {
jsonparse_next(parser);
jsonparse_next(parser);
callback_port = jsonparse_get_value_as_int(parser);
if(callback_port == 0) {
callback_port = CALLBACK_PORT;
}
update++;
} else if(jsonparse_strcmp_value(parser, "interval") == 0) {
jsonparse_next(parser);
jsonparse_next(parser);
callback_interval = jsonparse_get_value_as_int(parser);
if(callback_interval == 0) {
callback_interval = SEND_INTERVAL;
}
update++;
}
}
}
if(update && callback_json_path != NULL) {
#if WITH_UDP
if(strncmp(callback_proto, "udp", 3) == 0) {
json_ws_udp_setup(callback_host, callback_port);
}
#endif
ctimer_set(&periodic_timer, CLOCK_SECOND * callback_interval,
periodic, NULL);
}
return 0;
}
static struct jsontree_callback cfg_callback =
JSONTREE_CALLBACK(cfg_get, cfg_set);
JSONTREE_OBJECT_EXT(json_subscribe_callback,
JSONTREE_PAIR("host", &cfg_callback),
JSONTREE_PAIR("port", &cfg_callback),
JSONTREE_PAIR("path", &cfg_callback),
JSONTREE_PAIR("appdata", &cfg_callback),
JSONTREE_PAIR("proto", &cfg_callback),
JSONTREE_PAIR("interval", &cfg_callback));
/*---------------------------------------------------------------------------*/
static int
time_get(struct jsontree_context *js_ctx)
{
/* unix time */
char buf[20];
unsigned long time = json_time_offset + clock_seconds();
snprintf(buf, 20, "%lu", time);
jsontree_write_atom(js_ctx, buf);
return 0;
}
static int
time_set(struct jsontree_context *js_ctx, struct jsonparse_state *parser)
{
int type;
unsigned long time;
while((type = jsonparse_next(parser)) != 0) {
if(type == JSON_TYPE_PAIR_NAME) {
if(jsonparse_strcmp_value(parser, "time") == 0) {
jsonparse_next(parser);
jsonparse_next(parser);
time = jsonparse_get_value_as_long(parser);
json_time_offset = time - clock_seconds();
}
}
}
return 0;
}
struct jsontree_callback json_time_callback =
JSONTREE_CALLBACK(time_get, time_set);
/*---------------------------------------------------------------------------*/
#if PLATFORM_HAS_LEDS
#include "dev/leds.h"
static int
ws_leds_get(struct jsontree_context *js_ctx)
{
char buf[4];
unsigned char leds = leds_get();
snprintf(buf, 4, "%u", leds);
jsontree_write_atom(js_ctx, buf);
return 0;
}
static int
ws_leds_set(struct jsontree_context *js_ctx, struct jsonparse_state *parser)
{
int type, old_leds, new_leds;
while((type = jsonparse_next(parser)) != 0) {
if(type == JSON_TYPE_PAIR_NAME) {
if(jsonparse_strcmp_value(parser, "leds") == 0) {
jsonparse_next(parser);
jsonparse_next(parser);
new_leds = jsonparse_get_value_as_int(parser);
old_leds = leds_get();
leds_on(~old_leds & new_leds);
leds_off(old_leds & ~new_leds);
}
}
}
return 0;
}
struct jsontree_callback json_leds_callback =
JSONTREE_CALLBACK(ws_leds_get, ws_leds_set);
#endif /* PLATFORM_HAS_LEDS */
/*---------------------------------------------------------------------------*/
static struct httpd_ws_state *json_putchar_context;
static int
json_putchar(int c)
{
if(json_putchar_context != NULL &&
json_putchar_context->outbuf_pos < HTTPD_OUTBUF_SIZE) {
json_putchar_context->outbuf[json_putchar_context->outbuf_pos++] = c;
return c;
}
return 0;
}
static int putchar_size = 0;
static int
json_putchar_count(int c)
{
putchar_size++;
return c;
}
/*---------------------------------------------------------------------------*/
static
PT_THREAD(send_values(struct httpd_ws_state *s))
{
json_putchar_context = s;
PSOCK_BEGIN(&s->sout);
s->json.putchar = json_putchar;
s->outbuf_pos = 0;
if(s->json.values[0] == NULL) {
/* Nothing to do */
} else if(s->request_type == HTTPD_WS_POST &&
s->state == HTTPD_WS_STATE_OUTPUT) {
/* Set value */
struct jsontree_value *v;
struct jsontree_callback *c;
while((v = jsontree_find_next(&s->json, JSON_TYPE_CALLBACK)) != NULL) {
c = (struct jsontree_callback *)v;
if(c->set != NULL) {
struct jsonparse_state js;
jsonparse_setup(&js, s->inputbuf, s->content_len);
c->set(&s->json, &js);
}
}
memcpy(s->outbuf, "{\"Status\":\"OK\"}", 15);
s->outbuf_pos = 15;
} else {
/* Get value */
while(jsontree_print_next(&s->json) && s->json.path <= s->json.depth) {
if(s->outbuf_pos >= UIP_TCP_MSS) {
SEND_STRING(&s->sout, s->outbuf, UIP_TCP_MSS);
s->outbuf_pos -= UIP_TCP_MSS;
if(s->outbuf_pos > 0) {
memcpy(s->outbuf, &s->outbuf[UIP_TCP_MSS], s->outbuf_pos);
}
}
}
}
if(s->outbuf_pos > 0) {
SEND_STRING(&s->sout, s->outbuf, s->outbuf_pos);
s->outbuf_pos = 0;
}
PSOCK_END(&s->sout);
}
/*---------------------------------------------------------------------------*/
struct jsontree_value *
find_json_path(struct jsontree_context *json, const char *path)
{
struct jsontree_value *v;
const char *start;
const char *end;
int len;
v = json->values[0];
start = path;
do {
end = strchr(start, '/');
if(end == start) {
break;
}
if(end != NULL) {
len = end - start;
end++;
} else {
len = strlen(start);
}
if(v->type != JSON_TYPE_OBJECT) {
v = NULL;
} else {
struct jsontree_object *o;
int i;
o = (struct jsontree_object *)v;
v = NULL;
for(i = 0; i < o->count; i++) {
if(strncmp(start, o->pairs[i].name, len) == 0) {
v = o->pairs[i].value;
json->index[json->depth] = i;
json->depth++;
json->values[json->depth] = v;
json->index[json->depth] = 0;
break;
}
}
}
start = end;
} while(end != NULL && *end != '\0' && v != NULL);
json->callback_state = 0;
return v;
}
/*---------------------------------------------------------------------------*/
static int
calculate_json_size(const char *path, struct jsontree_value *v)
{
/* check size of JSON expression */
struct jsontree_context json;
json.values[0] = (v == NULL) ? (struct jsontree_value *)tree : v;
jsontree_reset(&json);
if(path != NULL) {
find_json_path(&json, path);
}
json.path = json.depth;
json.putchar = json_putchar_count;
putchar_size = 0;
while(jsontree_print_next(&json) && json.path <= json.depth);
return putchar_size;
}
/*---------------------------------------------------------------------------*/
httpd_ws_script_t
httpd_ws_get_script(struct httpd_ws_state *s)
{
struct jsontree_value *v;
s->json.values[0] = v = (struct jsontree_value *)tree;
jsontree_reset(&s->json);
if(s->filename[1] == '\0') {
/* Default page: show full JSON tree. */
} else {
v = find_json_path(&s->json, &s->filename[1]);
}
if(v != NULL) {
s->json.path = s->json.depth;
s->content_type = http_content_type_json;
return send_values;
}
return NULL;
}
/*---------------------------------------------------------------------------*/
#if JSON_POST_EXTRA_HEADER || WITH_COSM
static int
output_headers(struct httpd_ws_state *s, char *buffer, int buffer_size,
int index)
{
if(index == 0) {
#ifdef JSON_POST_EXTRA_HEADER
return snprintf(buffer, buffer_size, "%s\r\n", JSON_POST_EXTRA_HEADER);
} else if(index == 1) {
#endif
#if WITH_COSM
if(strncmp(callback_proto, "cosm", 4) == 0 && callback_appdata[0] != '\0') {
return snprintf(buffer, buffer_size, "X-PachubeApiKey:%s\r\n",
callback_appdata);
}
#endif
}
return 0;
}
#endif /* JSON_POST_EXTRA_HEADER || WITH_COSM */
/*---------------------------------------------------------------------------*/
static void
periodic(void *ptr)
{
struct httpd_ws_state *s;
int callback_size;
if(callback_json_path != NULL && strlen(callback_host) > 2) {
ctimer_restart(&periodic_timer);
if(strncmp(callback_proto, "http", 4) == 0) {
callback_size = calculate_json_size(callback_json_path, NULL);
s = httpd_ws_request(HTTPD_WS_POST, callback_host, NULL, callback_port,
callback_path, http_content_type_json,
callback_size, send_values);
if(s != NULL) {
PRINTF("PERIODIC POST %s\n", callback_json_path);
#if JSON_POST_EXTRA_HEADER
s->output_extra_headers = output_headers;
#endif
s->json.values[0] = (struct jsontree_value *)tree;
jsontree_reset(&s->json);
find_json_path(&s->json, callback_json_path);
s->json.path = s->json.depth;
} else {
PRINTF("PERIODIC CALLBACK FAILED\n");
}
#if WITH_COSM
} else if(strncmp(callback_proto, "cosm", 4) == 0) {
callback_size = calculate_json_size(NULL, (struct jsontree_value *)
&cosm_tree);
/* printf("JSON Size:%d\n", callback_size); */
s = httpd_ws_request(HTTPD_WS_PUT, callback_host, "api.pachube.com",
callback_port, callback_path,
http_content_type_json, callback_size, send_values);
/* host = cosm host */
/* path => path to datastream / data point */
s->output_extra_headers = output_headers;
s->json.values[0] = (struct jsontree_value *)&cosm_tree;
jsontree_reset(&s->json);
s->json.path = 0;
PRINTF("PERIODIC cosm callback: %d\n", callback_size);
#endif /* WITH_COSM */
}
#if WITH_UDP
else {
callback_size = calculate_json_size(callback_json_path, NULL);
PRINTF("PERIODIC UDP size: %d\n", callback_size);
json_ws_udp_send(tree, callback_json_path);
}
#endif /* WITH_UDP */
} else {
printf("PERIODIC CALLBACK - nothing todo\n");
}
}
/*---------------------------------------------------------------------------*/
void
json_ws_init(struct jsontree_object *json)
{
PRINTF("JSON INIT (callback %s every %u seconds)\n",
CALLBACK_PROTO, SEND_INTERVAL);
tree = json;
ctimer_set(&periodic_timer, CLOCK_SECOND * SEND_INTERVAL, periodic, NULL);
process_start(&httpd_ws_process, NULL);
#if WITH_UDP
if(strncmp(callback_proto, "udp", 3) == 0) {
json_ws_udp_setup(callback_host, callback_port);
}
#endif /* WITH_UDP */
}
/*---------------------------------------------------------------------------*/
void
json_ws_set_callback(const char *path)
{
callback_json_path = path;
ctimer_restart(&periodic_timer);
}
/*---------------------------------------------------------------------------*/