ARM mbed M2X API Client: The ARM mbed client library is used to send/receive data to/from AT&T's M2X service from mbed LPC1768 microcontrollers.

Dependents:   m2x-demo-all M2X_MTS_ACCEL_DEMO M2X_MTS_Accel M2X_K64F_ACCEL ... more

Files at this revision

API Documentation at this revision

Comitter:
citrusbyte
Date:
Mon Dec 28 12:48:19 2015 +0000
Parent:
15:2610823f7f2e
Child:
17:9db4a86b876a
Commit message:
Add TimeService implementation

Changed in this revision

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
TimeService.cpp Show annotated file Show diff for this revision Revisions of this file
TimeService.h Show annotated file Show diff for this revision Revisions of this file
--- a/M2XStreamClient.cpp	Mon Apr 27 15:40:13 2015 +0000
+++ b/M2XStreamClient.cpp	Mon Dec 28 12:48:19 2015 +0000
@@ -116,6 +116,73 @@
   return readStatusCode(true);
 }
 
+int M2XStreamClient::getTimestamp32(int32_t *ts) {
+  // The maximum value of signed 64-bit integer is 0x7fffffffffffffff,
+  // which is 9223372036854775807. It consists of 19 characters, so a
+  // buffer of 20 is definitely enough here
+  int length = 20;
+  char buffer[20];
+  int status = getTimestamp(buffer, &length);
+  if (status == 200) {
+    int32_t result = 0;
+    for (int i = 0; i < length; i++) {
+      result = result * 10 + (buffer[i] - '0');
+    }
+    if (ts != NULL) { *ts = result; }
+  }
+  return status;
+}
+
+int M2XStreamClient::getTimestamp(char* buffer, int *bufferLength) {
+  if (bufferLength == NULL) { return E_INVALID; }
+  if (_client->connect(_host, _port)) {
+    DBGLN("%s", "Connected to M2X server!");
+    _client->println("GET /v2/time/seconds HTTP/1.0");
+
+    writeHttpHeader(-1);
+  } else {
+    DBGLN("%s", "ERROR: Cannot connect to M2X server!");
+    return E_NOCONNECTION;
+  }
+  int status = readStatusCode(false);
+  if (status == 200) {
+    int length = readContentLength();
+    if (length < 0) {
+      close();
+      return length;
+    }
+    if (*bufferLength < length) {
+      *bufferLength = length;
+      return E_BUFFER_TOO_SMALL;
+    }
+    *bufferLength = length;
+    int index = skipHttpHeader();
+    if (index != E_OK) {
+      close();
+      return index;
+    }
+    index = 0;
+    while (index < length) {
+      DBG("%s", "Received Data: ");
+      while ((index < length) && _client->available()) {
+        buffer[index++] = _client->read();
+        DBG("%c", buffer[index - 1]);
+      }
+      DBGLNEND;
+
+      if ((!_client->connected()) &&
+          (index < length)) {
+        close();
+        return E_NOCONNECTION;
+      }
+
+      delay(200);
+    }
+  }
+  close();
+  return status;
+}
+
 static int write_delete_values(Print* print, const char* from,
                                const char* end) {
   int bytes = 0;
@@ -317,7 +384,7 @@
 }
 
 int M2XStreamClient::skipHttpHeader() {
-  return waitForString("\r\n\r\n");
+  return waitForString("\n\r\n");
 }
 
 void M2XStreamClient::close() {
--- a/M2XStreamClient.h	Mon Apr 27 15:40:13 2015 +0000
+++ b/M2XStreamClient.h	Mon Dec 28 12:48:19 2015 +0000
@@ -47,6 +47,25 @@
 static const int E_NOTREACHABLE = -3;
 static const int E_INVALID = -4;
 static const int E_JSON_INVALID = -5;
+static const int E_BUFFER_TOO_SMALL = -6;
+static const int E_TIMESTAMP_ERROR = -8;
+
+static inline bool m2x_status_is_success(int status) {
+  return (status == E_OK) || (status >= 200 && status <= 299);
+}
+
+static inline bool m2x_status_is_client_error(int status) {
+  return status >= 400 && status <= 499;
+}
+
+static inline bool m2x_status_is_server_error(int status) {
+  return status >= 500 && status <= 599;
+}
+
+static inline bool m2x_status_is_error(int status) {
+  return m2x_status_is_client_error(status) ||
+      m2x_status_is_server_error(status);
+}
 
 /*
  * +type+ indicates the value type: 1 for string, 2 for number
@@ -164,6 +183,55 @@
   // or equal to the end timestamp.
   int deleteValues(const char* deviceId, const char* streamName,
                    const char* from, const char* end);
+
+  // Fetches current timestamp in seconds from M2X server. Since we
+  // are using signed 32-bit integer as return value, this will only
+  // return valid results before 03:14:07 UTC on 19 January 2038. If
+  // the device is supposed to work after that, this function should
+  // not be used.
+  //
+  // The returned value will contain the status code(positive values)
+  // or the error code(negative values).
+  // In case of success, the current timestamp will be filled in the
+  // +ts+ pointer passed in as argument.
+  //
+  // NOTE: although returning uint32_t can give us a larger space,
+  // we prefer to cope with the unix convention here.
+  int getTimestamp32(int32_t* ts);
+
+  // Fetches current timestamp in seconds from M2X server.
+  // This function will return the timestamp as an integer literal
+  // in the provided buffer. Hence there's no problem working after
+  // 03:14:07 UTC on 19 January 2038. The drawback part here, is that
+  // you will have to work with 64-bit integer, which is not available
+  // on certain platform(such as Arduino), a bignum library or alike
+  // is needed in this case.
+  //
+  // Notice +bufferLength+ is supposed to contain the length of the
+  // buffer when calling this function. It is also the caller's
+  // responsibility to ensure the buffer is big enough, otherwise
+  // the library will return an error indicating the buffer is too
+  // small.
+  // While this is not accurate all the time, one trick here is to
+  // pass in 0 as the bufferLength, in which case we will always return
+  // the buffer-too-small error. However, the correct buffer length
+  // can be found this way so a secound execution is most likely to work
+  // (unless we are at the edge of the buffer length increasing, for
+  // example, when the timestamp jumps from 9999999999 to 10000000000,
+  // which is highly unlikely to happend). However, given that the
+  // maximum 64-bit integer can be stored in 19 bytes, there's not
+  // much need to use this trick.)
+  //
+  // The returned value will contain the status code(positive values)
+  // or the error code(negative values).
+  // In case of success, the current timestamp will be filled in the
+  // passed +buffer+ pointer, and the actual used buffer length will
+  // be returned in +bufferLength+ pointer.
+  // NOTE: as long as we can read the returned buffer length, it will
+  // be used to fill in the +bufferLength+ variable even though other
+  // errors occur(buffer is not enough, network is shutdown before
+  // reading the whole buffer, etc.)
+  int getTimestamp(char* buffer, int* bufferLength);
 private:
   Client* _client;
   const char* _key;
@@ -209,5 +277,6 @@
 };
 
 #include "M2XStreamClient_template.h"
+#include "TimeService.h"
 
 #endif  /* M2XStreamClient_h */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TimeService.cpp	Mon Dec 28 12:48:19 2015 +0000
@@ -0,0 +1,139 @@
+#include "M2XStreamClient.h"
+
+static int fill_iso8601_timestamp(int32_t seconds, int32_t milli,
+                                  char* buffer, int* length);
+
+TimeService::TimeService(M2XStreamClient* client) : _client(client) {
+}
+
+int TimeService::init() {
+  _timer.start();
+  return reset();
+}
+
+int TimeService::reset() {
+  int32_t ts;
+  int status = _client->getTimestamp32(&ts);
+
+  if (m2x_status_is_success(status)) {
+    _server_timestamp = ts;
+    _local_last_milli = _timer.read_ms();
+  }
+
+  return status;
+}
+
+int TimeService::getTimestamp(char* buffer, int* length) {
+  uint32_t now = _timer.read_ms();
+  if (now < _local_last_milli) {
+    // In case of a timestamp overflow(happens once every 50 days on
+    // Arduino), we reset the server timestamp recorded.
+    int status = reset();
+    if (!m2x_status_is_success(status)) { return status; }
+    now = _timer.read_ms();
+  }
+  if (now < _local_last_milli) {
+    // We have already reseted the timestamp, so this cannot happen
+    // (an HTTP request can take longer than 50 days to finished? You
+    // must be kidding here). Something else must be wrong here
+    return E_TIMESTAMP_ERROR;
+  }
+  uint32_t diff = now - _local_last_milli;
+  _local_last_milli = now;
+  _server_timestamp += (int32_t) (diff / 1000); // Milliseconds to seconds
+  return fill_iso8601_timestamp(_server_timestamp, (int32_t) (diff % 1000),
+                                buffer, length);
+}
+
+#define SIZE_ISO_8601 25
+static inline bool is_leap_year(int16_t y) {
+  return ((1970 + y) > 0) &&
+      !((1970 + y) % 4) &&
+      (((1970 + y) % 100) || !((1970 + y) % 400));
+}
+static inline int32_t days_in_year(int16_t y) {
+  return is_leap_year(y) ? 366 : 365;
+}
+static const uint8_t MONTH_DAYS[]={31,28,31,30,31,30,31,31,30,31,30,31};
+
+static int fill_iso8601_timestamp(int32_t timestamp, int32_t milli,
+                                  char* buffer, int* length) {
+  int16_t year;
+  int8_t month, month_length;
+  int32_t day;
+  int8_t hour, minute, second;
+
+  if (*length < SIZE_ISO_8601) {
+    *length = SIZE_ISO_8601;
+    return E_BUFFER_TOO_SMALL;
+  }
+
+  second = timestamp % 60;
+  timestamp /= 60; // now it is minutes
+
+  minute = timestamp % 60;
+  timestamp /= 60; // now it is hours
+
+  hour = timestamp % 24;
+  timestamp /= 24; // now it is days
+
+  year = 0;
+  day = 0;
+  while ((day += days_in_year(year)) <= timestamp) {
+    year++;
+  }
+  day -= days_in_year(year);
+  timestamp -= day; // now it is days in this year, starting at 0
+
+  day = 0;
+  month_length = 0;
+  for (month = 0; month < 12; month++) {
+    if (month == 1) {
+      // February
+      month_length = is_leap_year(year) ? 29 : 28;
+    } else {
+      month_length = MONTH_DAYS[month];
+    }
+
+    if (timestamp >= month_length) {
+      timestamp -= month_length;
+    } else {
+      break;
+    }
+  }
+  year = 1970 + year;
+  month++; // offset by 1
+  day = timestamp + 1;
+
+  int i = 0, j = 0;
+
+  // NOTE: It seems the snprintf implementation in Arduino has bugs,
+  // we have to manually piece the string together here.
+#define INT_TO_STR(v_, width_) \
+  for (j = 0; j < (width_); j++) { \
+    buffer[i + (width_) - 1 - j] = '0' + ((v_) % 10); \
+    (v_) /= 10; \
+  } \
+  i += (width_)
+
+  INT_TO_STR(year, 4);
+  buffer[i++] = '-';
+  INT_TO_STR(month, 2);
+  buffer[i++] = '-';
+  INT_TO_STR(day, 2);
+  buffer[i++] = 'T';
+  INT_TO_STR(hour, 2);
+  buffer[i++] = ':';
+  INT_TO_STR(minute, 2);
+  buffer[i++] = ':';
+  INT_TO_STR(second, 2);
+  buffer[i++] = '.';
+  INT_TO_STR(milli, 3);
+  buffer[i++] = 'Z';
+  buffer[i++] = '\0';
+
+#undef INT_TO_STR
+
+  *length = i;
+  return E_OK;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TimeService.h	Mon Dec 28 12:48:19 2015 +0000
@@ -0,0 +1,40 @@
+#ifndef TimeService_h
+#define TimeService_h
+
+class M2XStreamClient;
+
+// A ISO8601 timestamp generation service for M2X.
+// It uses the Time API provided by the M2X server to initialize
+// clock, then uses millis() function provided by Arduino to calculate
+// time advancements so as to reduce API query times.
+//
+// Right now, this service only works with 32-bit timestamp, meaning that
+// this service won't work after 03:14:07 UTC on 19 January 2038. However,
+// a similar service that uses 64-bit timestamp can be implemented following
+// the logic here.
+class TimeService {
+public:
+  TimeService(M2XStreamClient* client);
+
+  // Initialize the time service. Notice the TimeService instance is only
+  // working after calling this function successfully.
+  int init();
+
+  // Reset the internal recorded time by calling M2X Time API again. Normally,
+  // you don't need to call this manually. TimeService will handle Arduino clock
+  // overflow automatically
+  int reset();
+
+  // Fills ISO8601 formatted timestamp into the buffer provided. +length+ should
+  // contains the maximum supported length of the buffer when calling. For now,
+  // the buffer should be able to store 25 characters for a full ISO8601 formatted
+  // timestamp, otherwise, an error will be returned.
+  int getTimestamp(char* buffer, int* length);
+private:
+  M2XStreamClient* _client;
+  int32_t _server_timestamp;
+  uint32_t _local_last_milli;
+  Timer _timer;
+};
+
+#endif  /* TimeService_h */