/* * 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 * Joakim Eriksson * Joel Hoglund */ #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 #include #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); } /*---------------------------------------------------------------------------*/