branch with improvemnts

Fork of M2XStreamClient by AT&T M2X Team

Files at this revision

API Documentation at this revision

Comitter:
jb8414
Date:
Wed Feb 12 19:43:34 2014 +0000
Child:
1:4d7109bae9cf
Child:
2:7ea7ab05f120
Commit message:
initial commit from github revision b98a6d0

Changed in this revision

Client.cpp Show annotated file Show diff for this revision Revisions of this file
Client.h Show annotated file Show diff for this revision Revisions of this file
LocationParseFunctions.h Show annotated file Show diff for this revision Revisions of this file
M2XStreamClient.cpp Show annotated file Show diff for this revision Revisions of this file
M2XStreamClient.h Show annotated file Show diff for this revision Revisions of this file
M2XStreamClient_template.h Show annotated file Show diff for this revision Revisions of this file
NullPrint.h Show annotated file Show diff for this revision Revisions of this file
Print.cpp Show annotated file Show diff for this revision Revisions of this file
Print.h Show annotated file Show diff for this revision Revisions of this file
StreamParseFunctions.h Show annotated file Show diff for this revision Revisions of this file
Utility.cpp Show annotated file Show diff for this revision Revisions of this file
Utility.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Client.cpp	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,60 @@
+#include "Client.h"
+#include "mbed.h"
+
+#include <stdint.h>
+
+Client::Client() : _len(0), _sock() {
+}
+
+Client::~Client() {
+}
+
+int Client::connect(const char *host, uint16_t port) {
+  return _sock.connect(host, port) == 0;
+}
+
+size_t Client::write(uint8_t b) {
+  return write(&b, 1);
+}
+
+size_t Client::write(const uint8_t *buf, size_t size) {
+  _sock.set_blocking(false, 15000);
+  // NOTE: we know it's dangerous to cast from (const uint8_t *) to (char *),
+  // but we are trying to maintain a stable interface between the Arduino
+  // one and the mbed one. What's more, while TCPSocketConnection has no
+  // intention of modifying the data here, it requires us to send a (char *)
+  // typed data. So we belive it's safe to do the cast here.
+  return _sock.send_all(const_cast<char*>((const char*) buf), size);
+}
+
+int Client::available() {
+  if (_len > 0) { return 1; }
+  int ret = read(_buf, 1);
+  if (ret <= 0) { return 0; }
+  _len = ret;
+  return 1;
+}
+
+int Client::read() {
+  if (_len > 0) {
+    _len = 0;
+    return _buf[0];
+  }
+  return -1;
+}
+
+int Client::read(uint8_t *buf, size_t size) {
+  return _sock.receive_all((char*) buf, size);
+}
+
+void Client::flush() {
+  // does nothing, TCP stack takes care of this
+}
+
+void Client::stop() {
+  _sock.close();
+}
+
+uint8_t Client::connected() {
+  return _sock.is_connected();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Client.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,32 @@
+#ifndef Client_h
+#define Client_h
+
+#include "TCPSocketConnection.h"
+
+#include "Print.h"
+#include "Utility.h"
+
+/*
+ * TCP Client
+ */
+class Client : public Print {
+public:
+  Client();
+  ~Client();
+
+  virtual int connect(const char *host, uint16_t port);
+  virtual size_t write(uint8_t);
+  virtual size_t write(const uint8_t *buf, size_t size);
+  virtual int available();
+  virtual int read();
+  virtual void flush();
+  virtual void stop();
+  virtual uint8_t connected();
+private:
+  virtual int read(uint8_t *buf, size_t size);
+  uint8_t _buf[1];
+  uint8_t _len;
+  TCPSocketConnection _sock;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LocationParseFunctions.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,100 @@
+#ifndef LocationParseFunctions_h
+#define LocationParseFunctions_h
+
+// Data structures and functions used to parse locations
+
+#define LOCATION_BUF_LEN 20
+
+typedef struct {
+  uint16_t state;
+  char name_str[LOCATION_BUF_LEN + 1];
+  double latitude;
+  double longitude;
+  double elevation;
+  char timestamp_str[LOCATION_BUF_LEN + 1];
+  int index;
+
+  location_read_callback callback;
+  void* context;
+} location_parsing_context_state;
+
+#define WAITING_NAME 0x1
+#define WAITING_LATITUDE 0x2
+#define WAITING_LONGITUDE 0x4
+#define WAITING_ELEVATION 0x8
+#define WAITING_TIMESTAMP 0x10
+
+#define GOT_NAME 0x20
+#define GOT_LATITUDE 0x40
+#define GOT_LONGITUDE 0x80
+#define GOT_ELEVATION 0x100
+#define GOT_TIMESTAMP 0x200
+
+#define GOT_LOCATION (GOT_NAME | GOT_LATITUDE | GOT_LONGITUDE | GOT_ELEVATION | GOT_TIMESTAMP)
+#define TEST_GOT_LOCATION(state_) (((state_) & GOT_LOCATION) == GOT_LOCATION)
+
+#define TEST_IS_NAME(state_) (((state_) & (WAITING_NAME | GOT_NAME)) == WAITING_NAME)
+#define TEST_IS_LATITUDE(state_) (((state_) & (WAITING_LATITUDE | GOT_LATITUDE)) \
+                                  == WAITING_LATITUDE)
+#define TEST_IS_LONGITUDE(state_) (((state_) & (WAITING_LONGITUDE | GOT_LONGITUDE)) \
+                                   == WAITING_LONGITUDE)
+#define TEST_IS_ELEVATION(state_) (((state_) & (WAITING_ELEVATION | GOT_ELEVATION)) \
+                                   == WAITING_ELEVATION)
+#define TEST_IS_TIMESTAMP(state_) (((state_) & (WAITING_TIMESTAMP | GOT_TIMESTAMP)) \
+                                   == WAITING_TIMESTAMP)
+
+static void on_location_key_found(jsonlite_callback_context* context,
+                                  jsonlite_token* token) {
+  location_parsing_context_state* state =
+      (location_parsing_context_state*) context->client_state;
+  if (strncmp((const char*) token->start, "waypoints", 9) == 0) {
+    // only parses those locations in waypoints, skip the outer one
+    state->state = 0;
+  } else if (strncmp((const char*) token->start, "name", 4) == 0) {
+    state->state |= WAITING_NAME;
+  } else if (strncmp((const char*) token->start, "latitude", 8) == 0) {
+    state->state |= WAITING_LATITUDE;
+  } else if (strncmp((const char*) token->start, "longitude", 9) == 0) {
+    state->state |= WAITING_LONGITUDE;
+  } else if (strncmp((const char*) token->start, "elevation", 9) == 0) {
+    state->state |= WAITING_ELEVATION;
+  } else if (strncmp((const char*) token->start, "timestamp", 9) == 0) {
+    state->state |= WAITING_TIMESTAMP;
+  }
+}
+
+static void on_location_string_found(jsonlite_callback_context* context,
+                                     jsonlite_token* token) {
+  location_parsing_context_state* state =
+      (location_parsing_context_state*) context->client_state;
+
+  if (TEST_IS_NAME(state->state)) {
+    strncpy(state->name_str, (const char*) token->start,
+            MIN(token->end - token->start, LOCATION_BUF_LEN));
+    state->name_str[MIN(token->end - token->start, LOCATION_BUF_LEN)] = '\0';
+    state->state |= GOT_NAME;
+  } else if (TEST_IS_LATITUDE(state->state)) {
+    state->latitude = atof((const char*) token->start);
+    state->state |= GOT_LATITUDE;
+  } else if (TEST_IS_LONGITUDE(state->state)) {
+    state->longitude = atof((const char*) token->start);
+    state->state |= GOT_LONGITUDE;
+  } else if (TEST_IS_ELEVATION(state->state)) {
+    state->elevation = atof((const char*) token->start);
+    state->state |= GOT_ELEVATION;
+  } else if (TEST_IS_TIMESTAMP(state->state)) {
+    strncpy(state->timestamp_str, (const char*) token->start,
+            MIN(token->end - token->start, LOCATION_BUF_LEN));
+    state->timestamp_str[MIN(token->end - token->start, LOCATION_BUF_LEN)] = '\0';
+    state->state |= GOT_TIMESTAMP;
+  }
+
+  if (TEST_GOT_LOCATION(state->state)) {
+    state->callback(state->name_str, state->latitude, state->longitude,
+                    state->elevation, state->timestamp_str, state->index++,
+                    state->context);
+    state->state = 0;
+  }
+}
+
+#endif  /* LocationParseFunctions_h */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/M2XStreamClient.cpp	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,418 @@
+#include "M2XStreamClient.h"
+
+#include <jsonlite.h>
+
+#include "StreamParseFunctions.h"
+#include "LocationParseFunctions.h"
+
+const char* M2XStreamClient::kDefaultM2XHost = "api-m2x.att.com";
+
+int print_encoded_string(Print* print, const char* str);
+int tolower(int ch);
+
+#if defined(ARDUINO_PLATFORM) || defined(MBED_PLATFORM)
+int tolower(int ch)
+{
+  // Arduino and mbed use ASCII table, so we can simplify the implementation
+  if ((ch >= 'A') && (ch <= 'Z')) {
+    return (ch + 32);
+  }
+  return ch;
+}
+#else
+// For other platform, we use libc's tolower by default
+#include <ctype.h>
+#endif
+
+M2XStreamClient::M2XStreamClient(Client* client,
+                                 const char* key,
+                                 int case_insensitive,
+                                 const char* host,
+                                 int port) : _client(client),
+                                             _key(key),
+                                             _case_insensitive(case_insensitive),
+                                             _host(host),
+                                             _port(port),
+                                             _null_print() {
+}
+
+#define WRITE_QUERY_PART(client_, started_, name_, str_) { \
+  if (str_) { \
+    if (started_) { \
+      (client_)->print("&"); \
+    } else { \
+      (client_)->print("?"); \
+      started_ = true; \
+    } \
+    (client_)->print(name_ "="); \
+    (client_)->print(str_); \
+  } \
+  }
+
+int M2XStreamClient::fetchValues(const char* feedId, const char* streamName,
+                                 stream_value_read_callback callback, void* context,
+                                 const char* startTime, const char* endTime,
+                                 const char* limit) {
+  if (_client->connect(_host, _port)) {
+    bool query_started = false;
+
+    DBGLN("%s", "Connected to M2X server!");
+    _client->print("GET /v1/feeds/");
+    print_encoded_string(_client, feedId);
+    _client->print("/streams/");
+    print_encoded_string(_client, streamName);
+    _client->print("/values");
+
+    WRITE_QUERY_PART(_client, query_started, "start", startTime);
+    WRITE_QUERY_PART(_client, query_started, "end", endTime);
+    WRITE_QUERY_PART(_client, query_started, "limit", limit);
+
+    _client->println(" HTTP/1.0");
+    writeHttpHeader(-1);
+  } else {
+    DBGLN("%s", "ERROR: Cannot connect to M2X server!");
+    return E_NOCONNECTION;
+  }
+  int status = readStatusCode(false);
+  if (status == 200) {
+    readStreamValue(callback, context);
+  }
+
+  close();
+  return status;
+}
+
+int M2XStreamClient::readLocation(const char* feedId,
+                                  location_read_callback callback,
+                                  void* context) {
+  if (_client->connect(_host, _port)) {
+    DBGLN("%s", "Connected to M2X server!");
+    _client->print("GET /v1/feeds/");
+    print_encoded_string(_client, feedId);
+    _client->println("/location HTTP/1.0");
+
+    writeHttpHeader(-1);
+  } else {
+    DBGLN("%s", "ERROR: Cannot connect to M2X server!");
+    return E_NOCONNECTION;
+  }
+  int status = readStatusCode(false);
+  if (status == 200) {
+    readLocation(callback, context);
+  }
+
+  close();
+  return status;
+}
+
+// Encodes and prints string using Percent-encoding specified
+// in RFC 1738, Section 2.2
+int print_encoded_string(Print* print, const char* str) {
+  int bytes = 0;
+  for (int i = 0; str[i] != 0; i++) {
+    if (((str[i] >= 'A') && (str[i] <= 'Z')) ||
+        ((str[i] >= 'a') && (str[i] <= 'z')) ||
+        ((str[i] >= '0') && (str[i] <= '9')) ||
+        (str[i] == '-') || (str[i] == '_') ||
+        (str[i] == '.') || (str[i] == '~')) {
+      bytes += print->print(str[i]);
+    } else {
+      // Encode all other characters
+      bytes += print->print('%');
+      bytes += print->print(HEX(str[i] / 16));
+      bytes += print->print(HEX(str[i] % 16));
+    }
+  }
+  return bytes;
+}
+
+void M2XStreamClient::writePostHeader(const char* feedId,
+                                      const char* streamName,
+                                      int contentLength) {
+  _client->print("PUT /v1/feeds/");
+  print_encoded_string(_client, feedId);
+  _client->print("/streams/");
+  print_encoded_string(_client, streamName);
+  _client->println(" HTTP/1.0");
+
+  writeHttpHeader(contentLength);
+}
+
+void M2XStreamClient::writeHttpHeader(int contentLength) {
+  _client->println(USER_AGENT);
+  _client->print("X-M2X-KEY: ");
+  _client->println(_key);
+
+  _client->print("Host: ");
+  print_encoded_string(_client, _host);
+  if (_port != kDefaultM2XPort) {
+    _client->print(":");
+    // port is an integer, does not need encoding
+    _client->print(_port);
+  }
+  _client->println();
+
+  if (contentLength > 0) {
+    _client->println("Content-Type: application/json");
+    DBG("%s", "Content Length: ");
+    DBGLN("%d", contentLength);
+
+    _client->print("Content-Length: ");
+    _client->println(contentLength);
+  }
+  _client->println();
+}
+
+int M2XStreamClient::waitForString(const char* str) {
+  int currentIndex = 0;
+  if (str[currentIndex] == '\0') return E_OK;
+
+  while (true) {
+    while (_client->available()) {
+      char c = _client->read();
+      DBG("%c", c);
+
+      int cmp;
+      if (_case_insensitive) {
+        cmp = tolower(c) - tolower(str[currentIndex]);
+      } else {
+        cmp = c - str[currentIndex];
+      }
+
+      if ((str[currentIndex] == '*') || (cmp == 0)) {
+        currentIndex++;
+        if (str[currentIndex] == '\0') {
+          return E_OK;
+        }
+      } else {
+        // start from the beginning
+        currentIndex = 0;
+      }
+    }
+
+    if (!_client->connected()) {
+      DBGLN("%s", "ERROR: The client is disconnected from the server!");
+
+      close();
+      return E_DISCONNECTED;
+    }
+
+    delay(1000);
+  }
+  // never reached here
+  return E_NOTREACHABLE;
+}
+
+int M2XStreamClient::readStatusCode(bool closeClient) {
+  int responseCode = 0;
+  int ret = waitForString("HTTP/*.* ");
+  if (ret != E_OK) {
+    if (closeClient) close();
+    return ret;
+  }
+
+  // ret is not needed from here(since it must be E_OK), so we can use it
+  // as a regular variable now.
+  ret = 0;
+  while (true) {
+    while (_client->available()) {
+      char c = _client->read();
+      DBG("%c", c);
+
+      responseCode = responseCode * 10 + (c - '0');
+      ret++;
+      if (ret == 3) {
+        if (closeClient) close();
+        return responseCode;
+      }
+    }
+
+    if (!_client->connected()) {
+      DBGLN("%s", "ERROR: The client is disconnected from the server!");
+
+      if (closeClient) close();
+      return E_DISCONNECTED;
+    }
+
+    delay(1000);
+  }
+
+  // never reached here
+  return E_NOTREACHABLE;
+}
+
+int M2XStreamClient::readContentLength() {
+  int ret = waitForString("Content-Length: ");
+  if (ret != E_OK) {
+    return ret;
+  }
+
+  // From now on, ret is not needed, we can use it
+  // to keep the final result
+  ret = 0;
+  while (true) {
+    while (_client->available()) {
+      char c = _client->read();
+      DBG("%c", c);
+
+      if ((c == '\r') || (c == '\n')) {
+        return (ret == 0) ? (E_INVALID) : (ret);
+      } else {
+        ret = ret * 10 + (c - '0');
+      }
+    }
+
+    if (!_client->connected()) {
+      DBGLN("%s", "ERROR: The client is disconnected from the server!");
+
+      return E_DISCONNECTED;
+    }
+
+    delay(1000);
+  }
+
+  // never reached here
+  return E_NOTREACHABLE;
+}
+
+int M2XStreamClient::skipHttpHeader() {
+  return waitForString("\r\n\r\n");
+}
+
+void M2XStreamClient::close() {
+  // Eats up buffered data before closing
+  _client->flush();
+  _client->stop();
+}
+
+int M2XStreamClient::readStreamValue(stream_value_read_callback callback,
+                                     void* context) {
+  const int BUF_LEN = 32;
+  char buf[BUF_LEN];
+
+  int length = readContentLength();
+  if (length < 0) {
+    close();
+    return length;
+  }
+
+  int index = skipHttpHeader();
+  if (index != E_OK) {
+    close();
+    return index;
+  }
+  index = 0;
+
+  stream_parsing_context_state state;
+  state.state = state.index = 0;
+  state.callback = callback;
+  state.context = context;
+
+  jsonlite_parser_callbacks cbs = jsonlite_default_callbacks;
+  cbs.key_found = on_stream_key_found;
+  cbs.string_found = on_stream_string_found;
+  cbs.context.client_state = &state;
+
+  jsonlite_parser p = jsonlite_parser_init(jsonlite_parser_estimate_size(5));
+  jsonlite_parser_set_callback(p, &cbs);
+
+  jsonlite_result result = jsonlite_result_unknown;
+  while (index < length) {
+    int i = 0;
+
+    DBG("%s", "Received Data: ");
+    while ((i < BUF_LEN) && _client->available()) {
+      buf[i++] = _client->read();
+      DBG("%c", buf[i - 1]);
+    }
+    DBGLNEND;
+
+    if ((!_client->connected()) &&
+        (!_client->available()) &&
+        ((index + i) < length)) {
+      jsonlite_parser_release(p);
+      close();
+      return E_NOCONNECTION;
+    }
+
+    result = jsonlite_parser_tokenize(p, buf, i);
+    if ((result != jsonlite_result_ok) &&
+        (result != jsonlite_result_end_of_stream)) {
+      jsonlite_parser_release(p);
+      close();
+      return E_JSON_INVALID;
+    }
+
+    index += i;
+  }
+
+  jsonlite_parser_release(p);
+  close();
+  return (result == jsonlite_result_ok) ? (E_OK) : (E_JSON_INVALID);
+}
+
+int M2XStreamClient::readLocation(location_read_callback callback,
+                                  void* context) {
+  const int BUF_LEN = 40;
+  char buf[BUF_LEN];
+
+  int length = readContentLength();
+  if (length < 0) {
+    close();
+    return length;
+  }
+
+  int index = skipHttpHeader();
+  if (index != E_OK) {
+    close();
+    return index;
+  }
+  index = 0;
+
+  location_parsing_context_state state;
+  state.state = state.index = 0;
+  state.callback = callback;
+  state.context = context;
+
+  jsonlite_parser_callbacks cbs = jsonlite_default_callbacks;
+  cbs.key_found = on_location_key_found;
+  cbs.string_found = on_location_string_found;
+  cbs.context.client_state = &state;
+
+  jsonlite_parser p = jsonlite_parser_init(jsonlite_parser_estimate_size(5));
+  jsonlite_parser_set_callback(p, &cbs);
+
+  jsonlite_result result = jsonlite_result_unknown;
+  while (index < length) {
+    int i = 0;
+
+    DBG("%s", "Received Data: ");
+    while ((i < BUF_LEN) && _client->available()) {
+      buf[i++] = _client->read();
+      DBG("%c", buf[i - 1]);
+    }
+    DBGLNEND;
+
+    if ((!_client->connected()) &&
+        (!_client->available()) &&
+        ((index + i) < length)) {
+      jsonlite_parser_release(p);
+      close();
+      return E_NOCONNECTION;
+    }
+
+    result = jsonlite_parser_tokenize(p, buf, i);
+    if ((result != jsonlite_result_ok) &&
+        (result != jsonlite_result_end_of_stream)) {
+      jsonlite_parser_release(p);
+      close();
+      return E_JSON_INVALID;
+    }
+
+    index += i;
+  }
+
+  jsonlite_parser_release(p);
+  close();
+  return (result == jsonlite_result_ok) ? (E_OK) : (E_JSON_INVALID);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/M2XStreamClient.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,184 @@
+#ifndef M2XStreamClient_h
+#define M2XStreamClient_h
+
+#define MIN(a, b) (((a) > (b))?(b):(a))
+
+#define MBED_PLATFORM
+
+#ifdef ARDUINO_PLATFORM
+#include "Arduino.h"
+
+#define USER_AGENT "User-Agent: M2X Arduino Client/0.1"
+#endif
+
+#ifdef MBED_PLATFORM
+#include "mbed.h"
+
+#define USER_AGENT "User-Agent: M2X Mbed Client/0.1"
+#endif
+
+#include "Client.h"
+#include "NullPrint.h"
+
+#ifdef DEBUG
+#ifdef ARDUINO_PLATFORM
+#define DBG(fmt_, data_) Serial.print(data_)
+#define DBGLN(fmt_, data_) Serial.println(data_)
+#define DBGLNEND Serial.println()
+#endif  // ARDUINO_PLATFORM
+
+#ifdef MBED_PLATFORM
+#define DBG(fmt_, data_) printf((fmt_), (data_))
+#define DBGLN(fmt_, data_) printf((fmt_), (data_)); printf("\n")
+#define DBGLNEND printf("\n")
+#endif  // MBED_PLATFORM
+#else
+#define DBG(fmt_, data_)
+#define DBGLN(fmt_, data_)
+#define DBGLNEND
+#endif  // DEBUG
+
+#define HEX(t_) ((char) (((t_) > 9) ? ((t_) - 10 + 'A') : ((t_) + '0')))
+#define MAX_DOUBLE_DIGITS 7
+
+static const int E_OK = 0;
+static const int E_NOCONNECTION = -1;
+static const int E_DISCONNECTED = -2;
+static const int E_NOTREACHABLE = -3;
+static const int E_INVALID = -4;
+static const int E_JSON_INVALID = -5;
+
+typedef void (*stream_value_read_callback)(const char* at,
+                                           const char* value,
+                                           int index,
+                                           void* context);
+
+typedef void (*location_read_callback)(const char* name,
+                                       double latitude,
+                                       double longitude,
+                                       double elevation,
+                                       const char* timestamp,
+                                       int index,
+                                       void* context);
+
+class M2XStreamClient {
+public:
+  static const char* kDefaultM2XHost;
+  static const int kDefaultM2XPort = 80;
+
+  M2XStreamClient(Client* client,
+                  const char* key,
+                  int case_insensitive = 1,
+                  const char* host = kDefaultM2XHost,
+                  int port = kDefaultM2XPort);
+
+  // Post data stream value, returns the HTTP status code
+  template <class T>
+  int post(const char* feedId, const char* streamName, T value);
+
+  // Post multiple values to M2X all at once.
+  // +feedId+ - id of the feed to post values
+  // +streamNum+ - Number of streams to post
+  // +names+ - Array of stream names, the length of the array should
+  // be exactly +streamNum+
+  // +counts+ - Array of +streamNum+ length, each item in this array
+  // containing the number of values we want to post for each stream
+  // +ats+ - Timestamps for each value, the length of this array should
+  // be the some of all values in +counts+, for the first +counts[0]+
+  // items, the values belong to the first stream, for the following
+  // +counts[1]+ number of items, the values belong to the second stream,
+  // etc. Note timestamps are optional, if a value does not havee timestamp,
+  // we can simply put NULL here, or we can put NULl for +ats+, meaning
+  // none of the values has a timestamp
+  // +values+ - Values to post. This works the same way as +ats+, the
+  // first +counts[0]+ number of items contain values to post to the first
+  // stream, the succeeding +counts[1]+ number of items contain values
+  // for the second stream, etc. The length of this array should be
+  // the sum of all values in +counts+ array.
+  template <class T>
+  int postMultiple(const char* feedId, int streamNum,
+                   const char* names[], const int counts[],
+                   const char* ats[], T values[]);
+
+  // Fetch values for a particular data stream. Since memory is
+  // very limited on an Arduino, we cannot parse and get all the
+  // data points in memory. Instead, we use callbacks here: whenever
+  // a new data point is parsed, we call the callback using the values,
+  // after that, the values will be thrown away to make space for new
+  // values.
+  // Note that you can also pass in a user-specified context in this
+  // function, this context will be passed to the callback function
+  // each time we get a data point.
+  // For each data point, the callback will be called once. The HTTP
+  // status code will be returned. And the content is only parsed when
+  // the status code is 200.
+  int fetchValues(const char* feedId, const char* streamName,
+                  stream_value_read_callback callback, void* context,
+                  const char* startTime = NULL, const char* endTime = NULL,
+                  const char* limit = NULL);
+
+  // Update datasource location
+  // NOTE: On an Arduino Uno and other ATMEGA based boards, double has
+  // 4-byte (32 bits) precision, which is the same as float. So there's
+  // no natural double-precision floating number on these boards. With
+  // a float value, we have a precision of roughly 7 digits, that means
+  // either 5 or 6 digits after the floating point. According to wikipedia,
+  // a difference of 0.00001 will give us ~1.1132m distance. If this
+  // precision is good for you, you can use the double-version we provided
+  // here. Otherwise, you may need to use the string-version and do the
+  // actual conversion by yourselves.
+  // However, with an Arduino Due board, double has 8-bytes (64 bits)
+  // precision, which means you are free to use the double-version only
+  // without any precision problems.
+  // Returned value is the http status code.
+  template <class T>
+  int updateLocation(const char* feedId, const char* name,
+                     T latitude, T longitude, T elevation);
+
+  // Read location information for a feed. Also used callback to process
+  // data points for memory reasons. The HTTP status code is returned,
+  // response is only parsed when the HTTP status code is 200
+  int readLocation(const char* feedId, location_read_callback callback,
+                   void* context);
+private:
+  Client* _client;
+  const char* _key;
+  int _case_insensitive;
+  const char* _host;
+  int _port;
+  NullPrint _null_print;
+
+  // Writes the HTTP header part for updating a stream value
+  void writePostHeader(const char* feedId,
+                       const char* streamName,
+                       int contentLength);
+  // Writes HTTP header lines including M2X API Key, host, content
+  // type and content length(if the body exists)
+  void writeHttpHeader(int contentLength);
+  // Parses HTTP response header and return the content length.
+  // Note that this function does not parse all http headers, as long
+  // as the content length is found, this function will return
+  int readContentLength();
+  // Skips all HTTP response header part. Return minus value in case
+  // the connection is closed before we got all headers
+  int skipHttpHeader();
+  // Parses and returns the HTTP status code, note this function will
+  // return immediately once it gets the status code
+  int readStatusCode(bool closeClient);
+  // Waits for a certain string pattern in the HTTP header, and returns
+  // once the pattern is found. In the pattern, you can use '*' to denote
+  // any character
+  int waitForString(const char* str);
+  // Closes the connection
+  void close();
+  // Parses JSON response of stream value API, and calls callback function
+  // once we get a data point
+  int readStreamValue(stream_value_read_callback callback, void* context);
+  // Parses JSON response of location API, and calls callback function once
+  // we get a data point
+  int readLocation(location_read_callback callback, void* context);
+};
+
+#include "M2XStreamClient_template.h"
+
+#endif  /* M2XStreamClient_h */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/M2XStreamClient_template.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,133 @@
+#ifndef M2XStreamClient_template_h
+#define M2XStreamClient_template_h
+
+// Implementations of template functions
+
+int print_encoded_string(Print* print, const char* str);
+
+template <class T>
+int M2XStreamClient::post(const char* feedId, const char* streamName, T value) {
+  if (_client->connect(_host, _port)) {
+    DBGLN("%s", "Connected to M2X server!");
+    writePostHeader(feedId, streamName,
+                    //  for {"value": and }
+                    _null_print.print(value) + 10);
+    _client->print("{\"value\":");
+    _client->print(value);
+    _client->print("}");
+  } else {
+    DBGLN("%s", "ERROR: Cannot connect to M2X server!");
+    return E_NOCONNECTION;
+  }
+
+  return readStatusCode(true);
+}
+
+template <class T>
+inline int write_multiple_values(Print* print, int streamNum,
+                                 const char* names[], const int counts[],
+                                 const char* ats[], T values[]) {
+  int bytes = 0, value_index = 0;
+  bytes += print->print("{\"values\":{");
+  for (int i = 0; i < streamNum; i++) {
+    bytes += print->print("\"");
+    bytes += print->print(names[i]);
+    bytes += print->print("\":[");
+    for (int j = 0; j < counts[i]; j++) {
+      bytes += print->print("{");
+      if (ats && ats[value_index]) {
+        bytes += print->print("\"at\": \"");
+        bytes += print->print(ats[value_index]);
+        bytes += print->print("\",");
+      }
+      bytes += print->print("\"value\": \"");
+      bytes += print->print(values[value_index]);
+      bytes += print->print("\"}");
+      if (j < counts[i] - 1) { bytes += print->print(","); }
+      value_index++;
+    }
+    bytes += print->print("]");
+    if (i < streamNum - 1) { bytes += print->print(","); }
+  }
+  bytes += print->print("}}");
+  return bytes;
+}
+
+template <class T>
+int M2XStreamClient::postMultiple(const char* feedId, int streamNum,
+                                  const char* names[], const int counts[],
+                                  const char* ats[], T values[]) {
+  if (_client->connect(_host, _port)) {
+    DBGLN("%s", "Connected to M2X server!");
+    int length = write_multiple_values(&_null_print, streamNum, names,
+                                       counts, ats, values);
+    _client->print("POST /v1/feeds/");
+    print_encoded_string(_client, feedId);
+    _client->println(" HTTP/1.0");
+    writeHttpHeader(length);
+    write_multiple_values(_client, streamNum, names, counts, ats, values);
+  } else {
+    DBGLN("%s", "ERROR: Cannot connect to M2X server!");
+    return E_NOCONNECTION;
+  }
+  return readStatusCode(true);
+}
+
+template <class T>
+static int write_location_data(Print* print, const char* name,
+                               T latitude, T longitude,
+                               T elevation) {
+  int bytes = 0;
+  bytes += print->print("{\"name\":\"");
+  bytes += print->print(name);
+  bytes += print->print("\",\"latitude\":\"");
+  bytes += print->print(latitude);
+  bytes += print->print("\",\"longitude\":\"");
+  bytes += print->print(longitude);
+  bytes += print->print("\",\"elevation\":\"");
+  bytes += print->print(elevation);
+  bytes += print->print("\"}");
+  return bytes;
+}
+
+static int write_location_data(Print* print, const char* name,
+                               double latitude, double longitude,
+                               double elevation) {
+  int bytes = 0;
+  bytes += print->print("{\"name\":\"");
+  bytes += print->print(name);
+  bytes += print->print("\",\"latitude\":\"");
+  bytes += print->print(latitude, MAX_DOUBLE_DIGITS);
+  bytes += print->print("\",\"longitude\":\"");
+  bytes += print->print(longitude, MAX_DOUBLE_DIGITS);
+  bytes += print->print("\",\"elevation\":\"");
+  bytes += print->print(elevation);
+  bytes += print->print("\"}");
+  return bytes;
+}
+
+template <class T>
+int M2XStreamClient::updateLocation(const char* feedId,
+                                    const char* name,
+                                    T latitude,
+                                    T longitude,
+                                    T elevation) {
+  if (_client->connect(_host, _port)) {
+    DBGLN("%s", "Connected to M2X server!");
+
+    int length = write_location_data(&_null_print, name, latitude, longitude,
+                                     elevation);
+    _client->print("PUT /v1/feeds/");
+    print_encoded_string(_client, feedId);
+    _client->println("/location HTTP/1.0");
+
+    writeHttpHeader(length);
+    write_location_data(_client, name, latitude, longitude, elevation);
+  } else {
+    DBGLN("%s", "ERROR: Cannot connect to M2X server!");
+    return E_NOCONNECTION;
+  }
+  return readStatusCode(true);
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NullPrint.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,18 @@
+#ifndef NullPrint_h
+#define NullPrint_h
+
+#include "Print.h"
+
+// Null Print class used to calculate length to print
+class NullPrint : public Print {
+public:
+  virtual size_t write(uint8_t b) {
+    return 1;
+  }
+
+  virtual size_t write(const uint8_t* buf, size_t size) {
+    return size;
+  }
+};
+
+#endif  /* NullPrint_h */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Print.cpp	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,62 @@
+#include "Print.h"
+#include "mbed.h"
+
+#include <stdio.h>
+#include <string.h>
+
+size_t Print::write(const uint8_t* buf, size_t size) {
+  size_t ret = 0;
+  while (size--) {
+    ret += write(*buf++);
+  }
+  return ret;
+}
+
+size_t Print::print(const char* s) {
+  return write((const uint8_t*)s, strlen(s));
+}
+
+size_t Print::print(char c) {
+  return write(c);
+}
+
+size_t Print::print(int n) {
+  return print((long) n);
+}
+
+size_t Print::print(long n) {
+  char buf[8 * sizeof(long) + 1];
+  snprintf(buf, sizeof(buf), "%ld", n);
+  return print(buf);
+}
+
+// Digits are ignored for now
+size_t Print::print(double n, int digits) {
+  char buf[65];
+  snprintf(buf, sizeof(buf), "%g", n);
+  return print(buf);
+}
+
+size_t Print::println(const char* s) {
+  return print(s) + println();
+}
+
+size_t Print::println(char c) {
+  return print(c) + println();
+}
+
+size_t Print::println(int n) {
+  return print(n) + println();
+}
+
+size_t Print::println(long n) {
+  return print(n) + println();
+}
+
+size_t Print::println(double n, int digits) {
+  return print(n, digits) + println();
+}
+
+size_t Print::println() {
+  return print('\r') + print('\n');
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Print.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,26 @@
+#ifndef Print_h
+#define Print_h
+
+#include <stddef.h>
+#include <stdint.h>
+
+class Print {
+public:
+  size_t print(const char* s);
+  size_t print(char c);
+  size_t print(int n);
+  size_t print(long n);
+  size_t print(double n, int digits = 2);
+
+  size_t println(const char* s);
+  size_t println(char c);
+  size_t println(int n);
+  size_t println(long n);
+  size_t println(double n, int digits = 2);
+  size_t println();
+
+  virtual size_t write(uint8_t c) = 0;
+  virtual size_t write(const uint8_t* buf, size_t size);
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/StreamParseFunctions.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,68 @@
+#ifndef StreamParseFunctions_h
+#define StreamParseFunctions_h
+
+// Data structures and functions used to parse stream values
+
+#define STREAM_BUF_LEN 20
+
+typedef struct {
+  uint8_t state;
+  char at_str[STREAM_BUF_LEN + 1];
+  char value_str[STREAM_BUF_LEN + 1];
+  int index;
+
+  stream_value_read_callback callback;
+  void* context;
+} stream_parsing_context_state;
+
+#define WAITING_AT 0x1
+#define GOT_AT 0x2
+#define WAITING_VALUE 0x4
+#define GOT_VALUE 0x8
+
+#define GOT_STREAM (GOT_AT | GOT_VALUE)
+#define TEST_GOT_STREAM(state_) (((state_) & GOT_STREAM) == GOT_STREAM)
+
+#define TEST_IS_AT(state_) (((state_) & (WAITING_AT | GOT_AT)) == WAITING_AT)
+#define TEST_IS_VALUE(state_) (((state_) & (WAITING_VALUE | GOT_VALUE)) == \
+                               WAITING_VALUE)
+
+static void on_stream_key_found(jsonlite_callback_context* context,
+                                jsonlite_token* token)
+{
+  stream_parsing_context_state* state =
+      (stream_parsing_context_state*) context->client_state;
+  if (strncmp((const char*) token->start, "at", 2) == 0) {
+    state->state |= WAITING_AT;
+  } else if ((strncmp((const char*) token->start, "value", 5) == 0) &&
+             (token->start[5] != 's')) { // get rid of "values"
+    state->state |= WAITING_VALUE;
+  }
+}
+
+static void on_stream_string_found(jsonlite_callback_context* context,
+                                   jsonlite_token* token)
+{
+  stream_parsing_context_state* state =
+      (stream_parsing_context_state*) context->client_state;
+
+  if (TEST_IS_AT(state->state)) {
+    strncpy(state->at_str, (const char*) token->start,
+            MIN(token->end - token->start, STREAM_BUF_LEN));
+    state->at_str[MIN(token->end - token->start, STREAM_BUF_LEN)] = '\0';
+    state->state |= GOT_AT;
+  } else if (TEST_IS_VALUE(state->state)) {
+    strncpy(state->value_str, (const char*) token->start,
+            MIN(token->end - token->start, STREAM_BUF_LEN));
+    state->value_str[MIN(token->end - token->start, STREAM_BUF_LEN)] = '\0';
+    state->state |= GOT_VALUE;
+  }
+
+  if (TEST_GOT_STREAM(state->state)) {
+    state->callback(state->at_str, state->value_str,
+                    state->index++, state->context);
+    state->state = 0;
+  }
+}
+
+#endif  /* StreamParseFunctions_h */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utility.cpp	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,11 @@
+#include "Utility.h"
+
+void delay(int ms) {
+  wait_ms(ms);
+}
+
+char* strdup(const char* s) {
+  char* ret = (char*) malloc(strlen(s) + 1);
+  if (ret == NULL) { return ret;}
+  return strcpy(ret, s);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Utility.h	Wed Feb 12 19:43:34 2014 +0000
@@ -0,0 +1,18 @@
+#ifndef UTILITY_H_
+#define UTILITY_H_
+
+#include "mbed.h"
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void delay(int ms);
+char* strdup(const char* s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
\ No newline at end of file