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
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 */