* 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.
This commit is contained in:
parent
f177284a73
commit
51b73127e9
19 changed files with 3028 additions and 0 deletions
521
examples/ipv6/json-ws/json-ws.c
Normal file
521
examples/ipv6/json-ws/json-ws.c
Normal file
|
@ -0,0 +1,521 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
Loading…
Add table
Add a link
Reference in a new issue