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 16:7903152de19f, committed 2015-12-28
- 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
--- 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 */
