Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: WNCInterface_M2Xdemo ATT_WNCInterface_Info WNCInterface_HTTP_example Public_IoT_M2X_Cellular_Demo
Fork of M2XStreamClient by
Revision 0:f479e4f4db0e, committed 2014-02-12
- 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
--- /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
