diff --git a/library.properties b/library.properties index 6575c3f..5576aac 100644 --- a/library.properties +++ b/library.properties @@ -5,4 +5,4 @@ maintainer=Parse, LLC. sentence=A library that provides access to Parse paragraph=Provides convenience methods to access the REST API on Parse.com from Arduino. url=https://github.com/ParsePlatform/parse-embedded-sdks -architectures=avr,samd +architectures=avr,samd,esp8266 diff --git a/src/internal/ConnectionClient.h b/src/internal/ConnectionClient.h index 524405f..bc52b48 100644 --- a/src/internal/ConnectionClient.h +++ b/src/internal/ConnectionClient.h @@ -14,6 +14,11 @@ typedef WiFiClient ConnectionClient; #include typedef Process ConnectionClient; +#elif ARDUINO_ARCH_ESP8266 + +#include +typedef WiFiClientSecure ConnectionClient; + #endif #endif //__CONNECTION_CLIENT_H__ diff --git a/src/internal/ParseClient.h b/src/internal/ParseClient.h index 979ef5f..8819ce7 100644 --- a/src/internal/ParseClient.h +++ b/src/internal/ParseClient.h @@ -50,7 +50,7 @@ class ParseClient { void read(ConnectionClient* client, char* buf, int len); -#if defined (ARDUINO_SAMD_ZERO) +#if defined (ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_ESP8266) char lastPushTime[41]; // PUSH_TIME_MAX_LEN bool dataIsDirty; char pushBuff[5]; diff --git a/src/internal/ParsePush.h b/src/internal/ParsePush.h index 9b6355c..f8fcfcd 100644 --- a/src/internal/ParsePush.h +++ b/src/internal/ParsePush.h @@ -37,7 +37,7 @@ class ParsePush : public ParseResponse { protected: ParsePush(ConnectionClient* pushClient); -#if defined (ARDUINO_SAMD_ZERO) +#if defined (ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_ESP8266) char lookahead[5]; void setLookahead(const char *read_data); void read(); diff --git a/src/internal/ParseResponse.h b/src/internal/ParseResponse.h index 801a996..ac92ec0 100644 --- a/src/internal/ParseResponse.h +++ b/src/internal/ParseResponse.h @@ -42,7 +42,7 @@ class ParseResponse { bool isUserBuffer; int p; int resultCount; -#if defined (ARDUINO_SAMD_ZERO) +#if defined (ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_ESP8266) long responseLength; bool isChunked; bool firstObject; @@ -56,7 +56,7 @@ class ParseResponse { virtual void read(); void readWithTimeout(int maxSec); -#if defined (ARDUINO_SAMD_ZERO) +#if defined (ARDUINO_SAMD_ZERO) || defined(ARDUINO_ARCH_ESP8266) // Zero functions only - do nothing on Yun void readLine(char *buff, int sz); bool readJson(char *buff, int sz); diff --git a/src/internal/esp8266/ParseClient.cpp b/src/internal/esp8266/ParseClient.cpp new file mode 100644 index 0000000..9cad668 --- /dev/null +++ b/src/internal/esp8266/ParseClient.cpp @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#if defined (ARDUINO_ARCH_ESP8266) +#include "../ParseClient.h" +//#include "../../external/FlashStorage/FlashStorage.h" +#include + +// Set DEBUG to true to see serial debug output for the main stages +// of the Parse client. +const bool DEBUG = true; +const char* PARSE_API = "api.parse.com"; +const char* USER_AGENT = "arduino-esp8266.v.1.0.1"; +const char* CLIENT_VERSION = "1.0.3"; +const char* PARSE_PUSH = "push.parse.com"; +const unsigned short SSL_PORT = 443; + +struct KeysInternalStorage { + bool assigned; + char installationId[37]; + char sessionToken[41]; + char lastPushTime[41]; +}; + +// Reserve a portion of flash memory to store a "KeysInternalStorage" variable +// and call it "parse_flash_store". +//FlashStorage(parse_flash_store, KeysInternalStorage); + +/* + * !!! IMPORTANT !!! + * This function does not conform to RFC 4122, even though it returns + * a string formatted as an UUID. Do not use this to generate proper UUID! + */ +static String createNewInstallationId(void) { + static bool randInitialized = false; + char buff[40]; + + if (!randInitialized) { + struct timeval tm; + gettimeofday(&tm, NULL); + // Terrible choice for initialization, prone to collision + srand((unsigned)tm.tv_sec + (unsigned)tm.tv_usec); + randInitialized = true; + } + + snprintf(buff, sizeof(buff), + "%1x%1x%1x%1x%1x%1x%1x%1x-%1x%1x%1x%1x-%1x%1x%1x%1x-%1x%1x%1x%1x-%1x%1x%1x%1x%1x%1x%1x%1x%1x%1x%1x%1x", + rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, + rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16, rand()%16 + ); + return String(buff); +} + +static void sendAndEchoToSerial(WiFiClientSecure& client, const char *line) { + client.print(line); + if (Serial && DEBUG) + Serial.print(line); +} + +ParseClient::ParseClient() { + memset(applicationId, 0, sizeof(applicationId)); + memset(clientKey, 0, sizeof(clientKey)); + memset(installationId, 0, sizeof(installationId)); + memset(sessionToken, 0, sizeof(sessionToken)); + memset(lastPushTime, 0, sizeof(lastPushTime)); + lastHeartbeat = 0; + dataIsDirty = false; +} + +ParseClient::~ParseClient() { + end(); +} + +void ParseClient::begin(const char *applicationId, const char *clientKey) { + if (Serial && DEBUG) { + Serial.print("begin("); + Serial.print(applicationId ? applicationId : "NULL"); + Serial.print(", "); + Serial.print(clientKey ? clientKey : "NULL"); + Serial.println(")"); + } + + if(applicationId) { + strncpy(this->applicationId, applicationId, sizeof(this->applicationId)); + } + if (clientKey) { + strncpy(this->clientKey, clientKey, sizeof(this->clientKey)); + } + restoreKeys(); +} + +void ParseClient::setInstallationId(const char *installationId) { + if (installationId) { + if (strcmp(this->installationId, installationId)) + dataIsDirty = true; + strncpy(this->installationId, installationId, sizeof(this->installationId)); + } else { + if (this->installationId[0]) + dataIsDirty = true; + strncpy(this->installationId, "", sizeof(this->installationId)); + } +} + +const char* ParseClient::getInstallationId() { + if (!strlen(installationId)) { + setInstallationId(createNewInstallationId().c_str()); + char buff[40]; + + if (Serial && DEBUG) { + Serial.print("creating new installationId:"); + Serial.println(installationId); + } + + char content[120]; + snprintf(content, sizeof(content), "{\"installationId\": \"%s\", \"deviceType\": \"embedded\", \"parseVersion\": \"1.0.0\"}", installationId); + + ParseResponse response = sendRequest("POST", "/1/installations", content, ""); + if (Serial && DEBUG) { + Serial.print("response:"); + Serial.println(response.getJSONBody()); + } + } + saveKeys(); + return installationId; +} + +void ParseClient::setSessionToken(const char *sessionToken) { + if ((sessionToken != NULL) && (strlen(sessionToken) > 0 )) { + if (strcmp(this->sessionToken, sessionToken)) + dataIsDirty = true; + strncpy(this->sessionToken, sessionToken, sizeof(this->sessionToken)); + if (Serial && DEBUG) { + Serial.print("setting the session for installation:"); + Serial.println(installationId); + } + getInstallationId(); + ParseResponse response = sendRequest("GET", "/1/sessions/me", "", ""); + String installation = response.getString("installationId"); + if (!installation.length() && strlen(installationId)) { + sendRequest("PUT", "/1/sessions/me", "{}\r\n", ""); + } + } else { + if (sessionToken[0]) + dataIsDirty = true; + this->sessionToken[0] = 0; + } +} + +void ParseClient::clearSessionToken() { + setSessionToken(NULL); +} + +const char* ParseClient::getSessionToken() { + if (!strlen(sessionToken)) + return NULL; + return sessionToken; +} + +ParseResponse ParseClient::sendRequest(const char* httpVerb, const char* httpPath, const char* requestBody, const char* urlParams) { + return sendRequest(String(httpVerb), String(httpPath), String(requestBody), String(urlParams)); +} + +ParseResponse ParseClient::sendRequest(const String& httpVerb, const String& httpPath, const String& requestBody, const String& urlParams) { + char buff[91] = {0}; + //client.stop(); + + saveKeys(); + + if (Serial && DEBUG) { + Serial.print("sendRequest(\""); + Serial.print(httpVerb.c_str()); + Serial.print("\", \""); + Serial.print(httpPath.c_str()); + Serial.print("\", \""); + Serial.print(requestBody.c_str()); + Serial.print("\", \""); + Serial.print(urlParams.c_str()); + Serial.println("\")"); + } + + int retry = 3; + bool connected; + + while(!(connected = client.connect(PARSE_API, SSL_PORT)) && retry--) { + Serial.printf("connecting...%d\n", retry); + yield(); + } + + if (connected) { + if (Serial && DEBUG) { + Serial.println("connected to server"); + Serial.println(applicationId); + Serial.println(clientKey); + Serial.println(installationId); + } + if (urlParams.length() > 0) { + snprintf(buff, sizeof(buff) - 1, "%s %s?%s HTTP/1.1\r\n", httpVerb.c_str(), httpPath.c_str(), urlParams.c_str()); + } else { + snprintf(buff, sizeof(buff) - 1, "%s %s HTTP/1.1\r\n", httpVerb.c_str(), httpPath.c_str()); + } + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "Host: %s\r\n", PARSE_API); + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "X-Parse-Client-Version: %s\r\n", CLIENT_VERSION); + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "X-Parse-Application-Id: %s\r\n", applicationId); + sendAndEchoToSerial(client, buff); + snprintf(buff, sizeof(buff) - 1, "X-Parse-Client-Key: %s\r\n", clientKey); + sendAndEchoToSerial(client, buff); + + if (strlen(installationId) > 0) { + snprintf(buff, sizeof(buff) - 1, "X-Parse-Installation-Id: %s\r\n", installationId); + sendAndEchoToSerial(client, buff); + } + if (strlen(sessionToken) > 0) { + snprintf(buff, sizeof(buff) - 1, "X-Parse-Session-Token: %s\r\n", sessionToken); + sendAndEchoToSerial(client, buff); + } + if (requestBody.length() > 0 && httpVerb != "GET") { + requestBody; + sendAndEchoToSerial(client, "Content-Type: application/json; charset=utf-8\r\n"); + } else if (urlParams.length() > 0) { + sendAndEchoToSerial(client, "Content-Type: html/text\r\n"); + } + if (requestBody.length() > 0 && httpVerb != "GET") { + snprintf(buff, sizeof(buff) - 1, "Content-Length: %d\r\n", requestBody.length()); + sendAndEchoToSerial(client, buff); + } + sendAndEchoToSerial(client, "Connection: close\r\n"); + sendAndEchoToSerial(client, "\r\n"); + if (requestBody.length() > 0) { + sendAndEchoToSerial(client, requestBody.c_str()); + } + } else { + if (Serial && DEBUG) + Serial.println("failed to connect to server"); + } + ParseResponse response(&client); + return response; +} + +bool ParseClient::startPushService() { + pushClient.stop(); + + saveKeys(); + + if (Serial && DEBUG) + Serial.println("start push"); + + int retry = 3; + bool connected; + + while(!(connected = pushClient.connect(PARSE_PUSH, SSL_PORT)) && retry--); + + if (connected) { + if (Serial && DEBUG) + Serial.println("push started"); + char buff[256] = {0}; + snprintf(buff, sizeof(buff), + "{\"installation_id\":\"%s\", \"oauth_key\":\"%s\", " + "\"v\":\"e1.0.0\", \"last\":%s%s%s}\r\n{}\r\n", + installationId, + applicationId, + lastPushTime[0] ? "\"" : "", + lastPushTime[0] ? lastPushTime : "null", + lastPushTime[0] ? "\"" : ""); + sendAndEchoToSerial(pushClient, buff); + } else { + if (Serial && DEBUG) + Serial.println("failed to connect to push server"); + } +} + +ParsePush ParseClient::nextPush() { + ParsePush push(&pushClient); + if (pushBuff[0]) { + push.setLookahead(pushBuff); + memset(pushBuff, 0, sizeof(pushBuff)); + } + return push; +} + +bool ParseClient::pushAvailable() { + bool available = (pushClient.available() > 0); + // send keepalive if connected. + if (!available && pushClient.connected() && millis() - lastHeartbeat > 1000 * 60 * 12) { + pushClient.println("{}"); + lastHeartbeat = millis(); + } + if (pushBuff[0]) { + available = true; + } else { + for (int i = 0; i < sizeof(pushBuff) && (pushClient.available() > 0); ++i) { + pushBuff[i] = pushClient.read(); + } + if (!strncmp(pushBuff, "{}", 2)) { + int i = 2; + for (; pushBuff[i] == '\r' || pushBuff[i] == '\n'; ++i); + if (pushBuff[i]) { + int j = i;; + for (; pushBuff[i]; ++i) + pushBuff[i - j] = pushBuff[i]; + pushBuff[i - j] = 0; + } else { + memset(pushBuff, 0, sizeof(pushBuff)); + available = (pushClient.available() > 0); + } + } + } + return available; +} + +void ParseClient::stopPushService() { + pushClient.stop(); +} + +void ParseClient::end() { + stopPushService(); +} + +void ParseClient::saveKeys() { +#if 0 + if (dataIsDirty) { + KeysInternalStorage stored_keys = parse_flash_store.read(); + + stored_keys.assigned = true; + strcpy(stored_keys.installationId, installationId); + strcpy(stored_keys.sessionToken, sessionToken); + strcpy(stored_keys.lastPushTime, lastPushTime); + parse_flash_store.write(stored_keys); + if (Serial && DEBUG) { + Serial.println("ParseClient::saveKeys() : done."); + } + dataIsDirty = false; + } else { + if (Serial && DEBUG) { + Serial.println("ParseClient::saveKeys() : keys are not changed - skipping..."); + } + } +#endif +} + +void ParseClient::restoreKeys() { +#if 0 + KeysInternalStorage stored_keys = parse_flash_store.read(); + + if (stored_keys.assigned) { + strcpy(installationId, stored_keys.installationId); + strcpy(sessionToken, stored_keys.sessionToken); + strcpy(lastPushTime, stored_keys.lastPushTime); + if (Serial && DEBUG) { + Serial.println("ParseClient::restoreKeys() : done:"); + Serial.println(installationId); + Serial.println(sessionToken); + Serial.println(lastPushTime); + } + } else { + if (Serial && DEBUG) { + Serial.println("ParseClient::restoreKeys() : nothing is stored."); + } + } +#endif +} + +void ParseClient::saveLastPushTime(char *time) { + dataIsDirty = true; + strcpy(lastPushTime, time); + saveKeys(); +} + + +ParseClient Parse; + +#endif // ARDUINO_ARCH_ESP8266 diff --git a/src/internal/esp8266/ParsePush.cpp b/src/internal/esp8266/ParsePush.cpp new file mode 100644 index 0000000..5c117a8 --- /dev/null +++ b/src/internal/esp8266/ParsePush.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#if defined (ARDUINO_ARCH_ESP8266) + +#include "../ParsePush.h" +#include "../ParseClient.h" +#include "../ParseUtils.h" + +ParsePush::ParsePush(WiFiClientSecure* pushClient) : ParseResponse(pushClient) { + memset(lookahead, 0, sizeof(lookahead)); +} + +void ParsePush::close() { + freeBuffer(); +} + +void ParsePush::setLookahead(const char *read_data) { + strncpy(lookahead, read_data, sizeof(lookahead)); +} + +void ParsePush::read() { + if (buf == NULL) { + bufSize = BUFSIZE; + buf = new char[bufSize]; + } + + if (p == bufSize - 1) { + return; + } + + if (p == 0) { + memset(buf, 0, bufSize); + for (; lookahead[p]; ++p) + buf[p] = lookahead[p]; + lookahead[0] = 0; + } + + char c; + while (client->connected()) { + if (client->available()) { + c = client->read(); + if (c == '\n') c = '\0'; + if (p < bufSize - 1) { + *(buf + p) = c; + p++; + } + if (c == '\0') break; + } + } + p = 0; + char time[41]; + if (ParseUtils::getStringFromJSON(buf, "time", time, sizeof(time))) { + Parse.saveLastPushTime(time); + } +} + +#endif // ARDUINO_ARCH_ESP8266 + diff --git a/src/internal/esp8266/ParseResponse.cpp b/src/internal/esp8266/ParseResponse.cpp new file mode 100644 index 0000000..1a2552b --- /dev/null +++ b/src/internal/esp8266/ParseResponse.cpp @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2015, Parse, LLC. All rights reserved. + * + * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, + * copy, modify, and distribute this software in source code or binary form for use + * in connection with the web services and APIs provided by Parse. + * + * As with any software that integrates with the Parse platform, your use of + * this software is subject to the Parse Terms of Service + * [https://www.parse.com/about/terms]. This copyright notice shall be + * included in all copies or substantial portions of the software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#if defined (ARDUINO_ARCH_ESP8266) + +#include "../ParseResponse.h" +#include "../ParseInternal.h" + +static const char kHttpOK[] = "HTTP/1.1 200 OK"; +static const char kContentLength[] = "Content-Length:"; +static const char kChunkedEncoding[] = "transfer-encoding: chunked"; +// Fortunately we do not need to support *any* JSON, only the one generated by Parse. +static const char kResultsStart[] = "{\"results\":["; +static const int kJsonResponseMaxSize = 256; +static const int kQueryTimeout = 5000; +static const int kBufferSize = 1024; + +// Uncomment following line if you want to debug query response with serial output. +// #define DEBUG_RESPONSE + +ParseResponse::ParseResponse(ConnectionClient* client) { + buf = NULL; + tmpBuf = NULL; + p = 0; + resultCount = -1; + bufSize = 0; + isUserBuffer = false; + isChunked = false; + responseLength = 0; + dataDone = false; + this->client = client; + bufferPos = kBufferSize; + lastRead = -1; +} + +ParseResponse::~ParseResponse() { + close(); +} + +void ParseResponse::setBuffer(char* buffer, int size) { + if (!buffer || size <= 0) { + return; + } + + buf = buffer; + bufSize = size; + isUserBuffer = true; + memset(buf, 0, bufSize); +} + +int ParseResponse::available() { + return client->available(); +} + +void ParseResponse::read() { + if (dataDone) + return; + if (buf == NULL) { + bufSize = BUFSIZE; + buf = new char[bufSize]; + memset(buf, 0, bufSize); + } + + if (p == bufSize - 1) { + return; + } + + memset(buf + p, 0, bufSize - p); + + char c; + int i; + + bool data = false; + int count = 0; + + while (client->connected()) { + delay(1); + while (client->available()) { + c = client->read(); + if (c != '\r') { // filter out '\r' character + if (data) { + if (p < bufSize - 1) { + *(buf + p) = c; + p++; + } + } + // filter out the headers + if (c == '\n') count++; else count = 0; + if (count >= 2) data = true; + } + } + } + dataDone = 1; +} + +void ParseResponse::readLine(char *buff, int sz) { + memset(buff, 0, sz); +#ifdef DEBUG_RESPONSE + Serial.print("Read line:"); +#endif + for (int i = 0; client->available(); ++i) { + char c = client->read(); + if (c == '\r') { + client->read(); // read /n + return; + } + if (c == '\n') { + return; + } +#ifdef DEBUG_RESPONSE + Serial.print(c); +#endif + if (i < sz - 1) + buff[i] = c; + } +#ifdef DEBUG_RESPONSE + Serial.println(""); +#endif +} + +bool ParseResponse::readJson(char *buff, int sz) { + memset(buff, 0, sz); + + int read_bytes = 0; + + bool res = readJsonInternal(buff, sz, &read_bytes, '\0'); + if (!res) { +#ifdef DEBUG_RESPONSE + Serial.print("Failed"); + Serial.println(buff); +#endif + } + return res; +} + +bool ParseResponse::readJsonInternal(char *buff, int sz, int* read_bytes, char started) { + // It is our own JSON, so we can be *very* strict in regars to format. + char in_string = '\0'; + int ch; + int i; + + char tmp[32]; + + for (i = 0; ; ++i) { + bool skip = false; + if ((ch = readChunkedData(kQueryTimeout)) < 0) { + *read_bytes = i; + return false; + } + switch (ch) { + case '\"': + case '\'': + if (in_string == ch) + in_string = '\0'; + else if (in_string == '\0') + in_string = ch; + break; + case ',': + if (in_string) + break; + if (!started) { + --i; // skip + skip = true; + } + break; + case '[': + case '{': + { + if (in_string) + break; + if (!started) { + started = ch; + break; + } + int added = 0; + if (i < sz) + buff[i] = ch; + bool success = readJsonInternal(buff + i + 1, sz - i - 1, &added, ch); + if (!success) + return false; + i += added; + skip = true; + } break; + case '}': + case ']': + { + if (in_string) + break; + if (!started) + return false; + if ((started == '[' && ch != ']') || (started == '{' && ch != '}')) + return false; + if (i < sz) + buff[i] = ch; + *read_bytes = i + 1; + return true; + } break; + } + if (!skip && i < sz - 1) + buff[i] = ch; + } +} + +#ifdef DEBUG_RESPONSE +void printData(char *d, int sz, int offset) { + char t1[32]; + sprintf(t1,"\r\n%d %04x[->", sz, offset); + Serial.print(t1); + char tmp[4] = {0}; + for (int i = 0; i < sz; ++i) { + if (d[i] >= 32 && d[i] <= 127) + tmp[0] = d[i]; + else + tmp[0] = '?'; + Serial.print(tmp); + } + Serial.println("<-]"); +} +#endif + +int ParseResponse::readChunkedData(int timeout) { + + char snum[16]; + + if (bufferPos == kBufferSize || (lastRead > 0 && bufferPos == lastRead)) { + for (int i = 0; i < timeout && !client->available(); ++i) { + delay(1); + } + + if (responseLength <= 0) { + readLine(snum, sizeof(snum)); + readLine(snum, sizeof(snum)); + char *tmp; +#ifdef DEBUG_RESPONSE + Serial.println(""); + Serial.print("Next chunk:"); + Serial.println(snum); +#endif + responseLength = strtol(snum, &tmp, 16); + if (!responseLength) + return -1; + } + if (client->available()) { + int to_read = responseLength > kBufferSize ? kBufferSize : responseLength; + bufferPos = 0; + int sz = client->read((uint8_t *)chunkedBuffer, to_read); + lastRead = sz; +#ifdef DEBUG_RESPONSE + Serial.println(); + Serial.print("Read: "); + Serial.println(sz); + Serial.println(); + Serial.print("bufferPos: "); + Serial.println(bufferPos); + Serial.println(); + Serial.print("to_read: "); + Serial.println(to_read); + Serial.println(); + Serial.print("responseLength: "); + Serial.println(responseLength); + printData(chunkedBuffer, sz, bufferPos); +#endif + if (sz <= 0) + return -1; + responseLength -= sz; + } + } + int ch = chunkedBuffer[bufferPos++]; + return ch; +} + +int ParseResponse::getErrorCode() { + return getInt("code"); +} + +const char* ParseResponse::getJSONBody() { + read(); + return buf; +} + +const char* ParseResponse::getString(const char* key) { + read(); + if (!tmpBuf) { + tmpBuf = new char[64]; + } + memset(tmpBuf, 0, 64); + ParseUtils::getStringFromJSON(buf, key, tmpBuf, 64); + return tmpBuf; +} + +int ParseResponse::getInt(const char* key) { + read(); + return ParseUtils::getIntFromJSON(buf, key); +} + +double ParseResponse::getDouble(const char* key) { + read(); + return ParseUtils::getFloatFromJSON(buf, key); +} + +bool ParseResponse::getBoolean(const char* key) { + read(); + return ParseUtils::getBooleanFromJSON(buf, key); +} + +void ParseResponse::readWithTimeout(int maxSec) { + while((!available()) && (maxSec--)) { // wait till response + delay(1000); + } + read(); +} + +bool ParseResponse::nextObject() { + if(resultCount <= 0) { + count(); + } + + if(resultCount <= 0) { + return false; + } + + if (firstObject) { + firstObject = false; + return true; + } else { + return readJson(buf, bufSize); + } +} + +int ParseResponse::count() { + if (resultCount != -1) + return resultCount; + char buff[128]; + + resultCount = 0; + + bool first_line = true; + bool ok = false; + bool done = false; + char *ptr; + long len = 0; + while (client->connected() && !done) { + delay(1); + while (client->available()) { + readLine(buff, sizeof(buff)); + if (first_line) { + if (!strcmp(kHttpOK, buff)) + ok = true; + first_line = false; + } +#ifdef DEBUG_RESPONSE + Serial.print("H->"); + Serial.println(buff); +#endif + if (!strcmp(kChunkedEncoding, buff)) { + isChunked = true; + } else if (!strncmp(kContentLength, buff, sizeof(kContentLength))) { + responseLength = strtol(buff + sizeof(kContentLength), &ptr, 10); + } else if (!buff[0]) { + if (isChunked && client->available()) { + readLine(buff, sizeof(buff)); + responseLength = strtol(buff, &ptr, 16); +#ifdef DEBUG_RESPONSE + Serial.print("First chunk->"); + Serial.println(buff); +#endif + } + done = true; + break; + } + } + } + long persistentResponseLength = responseLength; // responseLength is modified by calls to readChunkedData +#ifdef DEBUG_RESPONSE + sprintf(buff, "Ok:%s Length:%d Chunked:%s", ok ? "y" : "n", responseLength, isChunked ? "y" : "n"); + Serial.println(buff); +#endif + done = false; + freeBuffer(); + setBuffer(new char[kJsonResponseMaxSize], kJsonResponseMaxSize); + dataDone = true; + + for (int i = 0; i < sizeof(kResultsStart) - 1; ++i) { + char c = readChunkedData(kQueryTimeout); + if (c != kResultsStart[i]) { +#ifdef DEBUG_RESPONSE + Serial.println("Malformed response!"); +#endif + resultCount = -1; + return -1; + } + } + + if (!readJson(buf, bufSize)) { +#ifdef DEBUG_RESPONSE + Serial.println("no results"); +#endif + resultCount = 0; + return 0; + } + + int tmplen = strlen(buf); + firstObject = true; + if (tmplen > 0) + resultCount = persistentResponseLength / tmplen; + if (isChunked) + resultCount *= 2; + if (!resultCount) + resultCount = 1; + + return resultCount; +} + +void ParseResponse::freeBuffer() { + if (!isUserBuffer) { // only free non-user buffer + delete[] buf; + buf = NULL; + } + if (tmpBuf) { + delete[] tmpBuf; + tmpBuf = NULL; + } +} + +void ParseResponse::close() { + freeBuffer(); +} + +#endif // ARDUINO_ARCH_ESP8266