|
| 1 | +// The MIT License (MIT) |
| 2 | +// |
| 3 | +// Copyright (c) 2015 Adafruit Industries |
| 4 | +// Author: Tony DiCola |
| 5 | +// |
| 6 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | +// of this software and associated documentation files (the "Software"), to deal |
| 8 | +// in the Software without restriction, including without limitation the rights |
| 9 | +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | +// copies of the Software, and to permit persons to whom the Software is |
| 11 | +// furnished to do so, subject to the following conditions: |
| 12 | +// |
| 13 | +// The above copyright notice and this permission notice shall be included in all |
| 14 | +// copies or substantial portions of the Software. |
| 15 | +// |
| 16 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 | +// SOFTWARE. |
| 23 | +#include "Adafruit_IO_Client.h" |
| 24 | + |
| 25 | +bool Adafruit_IO_Client::send(const char* feed, const char* value, |
| 26 | + const char* key, bool quoted) { |
| 27 | + // Make HTTP POST to send feed data as JSON object. |
| 28 | + |
| 29 | + // First make sure a connection to the service is available. |
| 30 | + if (!connected()) { |
| 31 | + DEBUG_PRINTLN(F("Failed to connect to service!")); |
| 32 | + return false; |
| 33 | + } |
| 34 | + |
| 35 | + // Compute size of request. |
| 36 | + uint16_t len = 10 + strlen(value); |
| 37 | + if (quoted) { |
| 38 | + len += 2; |
| 39 | + } |
| 40 | + |
| 41 | + // Send HTTP POST and headers. |
| 42 | + _client.print(F("POST /api/feeds/")); |
| 43 | + _client.print(feed); |
| 44 | + _client.print(F("/data/send HTTP/1.1\r\n")); |
| 45 | + sendHeaders(key); |
| 46 | + _client.print(F("Content-Type: application/json\r\n")); |
| 47 | + _client.print(F("Content-Length: ")); |
| 48 | + _client.print(len, DEC); |
| 49 | + _client.print(F("\r\n\r\n")); |
| 50 | + |
| 51 | + // Send HTTP POST data. |
| 52 | + _client.print(F("{\"value\":")); |
| 53 | + if (quoted) { |
| 54 | + _client.print('"'); |
| 55 | + _client.print(value); |
| 56 | + _client.print('"'); |
| 57 | + } |
| 58 | + else { |
| 59 | + _client.print(value); |
| 60 | + } |
| 61 | + _client.print('}'); |
| 62 | + |
| 63 | + // Serial.println("WRITTEN"); |
| 64 | + // char c = 0; |
| 65 | + // int timeout=5000; |
| 66 | + // while (timeout > 0) { |
| 67 | + // if (!_client.connected()) { |
| 68 | + // Serial.println("DISCONNECTED!"); |
| 69 | + // return false; |
| 70 | + // } |
| 71 | + // while (_client.available()) { |
| 72 | + // char c = _client.read(); |
| 73 | + // Serial.print(c); |
| 74 | + // } |
| 75 | + // timeout -= 10; |
| 76 | + // delay(10); |
| 77 | + // } |
| 78 | + // Serial.println("DONE!"); |
| 79 | + // _client.stop(); |
| 80 | + // return false; |
| 81 | + |
| 82 | + // Now wait to read response (up to the client's configured stream timeout). |
| 83 | + // First read the HTTP/1.1 response. |
| 84 | + char recvbuffer[IO_CLIENT_RECV_SIZE] = {0}; |
| 85 | + if ((_client.readBytesUntil(' ', recvbuffer, sizeof(recvbuffer)) != 8) || |
| 86 | + (strcmp_P(recvbuffer, PSTR("HTTP/1.1")) != 0)) { |
| 87 | + DEBUG_PRINTLN(F("Failed to find expected HTTP/1.1 response!")); |
| 88 | + _client.stop(); |
| 89 | + return false; |
| 90 | + } |
| 91 | + // Now read the status code and expect a 200-level response. |
| 92 | + memset(recvbuffer, 0, sizeof(recvbuffer)); |
| 93 | + if ((_client.readBytesUntil(' ', recvbuffer, sizeof(recvbuffer)-1) != 3) || |
| 94 | + (recvbuffer[0] != '2')) { |
| 95 | + DEBUG_PRINT(F("HTTP POST failed with error code: ")); |
| 96 | + DEBUG_PRINTLN(recvbuffer); |
| 97 | + _client.stop(); |
| 98 | + return false; |
| 99 | + } |
| 100 | + |
| 101 | + // Ignore parsing the response data for now. Close connection and return |
| 102 | + // success. |
| 103 | + _client.stop(); |
| 104 | + return true; |
| 105 | +} |
| 106 | + |
| 107 | +FeedData Adafruit_IO_Client::receive(const char* feed, const char* key) { |
| 108 | + // Make HTTP GET request to read latest feed item and then parse response |
| 109 | + // into FeedData object. |
| 110 | + |
| 111 | + // First make sure a connection to the service is available. |
| 112 | + if (!connected()) { |
| 113 | + DEBUG_PRINTLN(F("Failed to connect to service!")); |
| 114 | + return FeedData(); |
| 115 | + } |
| 116 | + |
| 117 | + // Send HTTP GET and headers. |
| 118 | + _client.print(F("GET /api/feeds/")); |
| 119 | + _client.print(feed); |
| 120 | + _client.print(F("/data/last.txt HTTP/1.1\r\n")); |
| 121 | + sendHeaders(key); |
| 122 | + _client.print(F("Accept: text/plain\r\n\r\n")); |
| 123 | + |
| 124 | + // Parse HTTP GET response. |
| 125 | + // First read the HTTP/1.1 response. |
| 126 | + char recvbuffer[IO_CLIENT_RECV_SIZE] = {0}; |
| 127 | + if ((_client.readBytesUntil(' ', recvbuffer, sizeof(recvbuffer)) != 8) || |
| 128 | + (strcmp_P(recvbuffer, PSTR("HTTP/1.1")) != 0)) { |
| 129 | + DEBUG_PRINTLN(F("Failed to find expected HTTP/1.1 response!")); |
| 130 | + _client.stop(); |
| 131 | + return FeedData(); |
| 132 | + } |
| 133 | + // Now read the status code and expect a 200-level response. |
| 134 | + memset(recvbuffer, 0, sizeof(recvbuffer)); |
| 135 | + if ((_client.readBytesUntil(' ', recvbuffer, sizeof(recvbuffer)-1) != 3) || |
| 136 | + (recvbuffer[0] != '2')) { |
| 137 | + DEBUG_PRINT(F("HTTP GET failed with error code: ")); |
| 138 | + DEBUG_PRINTLN(recvbuffer); |
| 139 | + _client.stop(); |
| 140 | + return FeedData(); |
| 141 | + } |
| 142 | + // Read the rest of the line. |
| 143 | + if (!_client.find("\r\n")) { |
| 144 | + DEBUG_PRINT(F("Unexpected HTTP GET response!")); |
| 145 | + _client.stop(); |
| 146 | + return FeedData(); |
| 147 | + } |
| 148 | + // Now parse all the header lines and look for an explicit content length. |
| 149 | + // If no content length is found then assume a chunked transfer encoding. |
| 150 | + uint16_t len = 0; |
| 151 | + while (true) { |
| 152 | + char c = _client.peek(); |
| 153 | + // Check for \r\n blank line to signify end of headers. |
| 154 | + if (c == '\r') { |
| 155 | + if (!_client.find("\r\n")) { |
| 156 | + // Something unexpected, fail. |
| 157 | + DEBUG_PRINT(F("Expected blank line after headers!")); |
| 158 | + _client.stop(); |
| 159 | + return FeedData(); |
| 160 | + } |
| 161 | + // Else found the end of the headers so stop parsing them. |
| 162 | + break; |
| 163 | + } |
| 164 | + // Parse a header name. |
| 165 | + char recvbuffer[IO_CLIENT_RECV_SIZE] = {0}; |
| 166 | + if (!_client.readBytesUntil(':', recvbuffer, sizeof(recvbuffer)-1)) { |
| 167 | + DEBUG_PRINT(F("Expected header name!")); |
| 168 | + _client.stop(); |
| 169 | + return FeedData(); |
| 170 | + } |
| 171 | + // Check for content-length header and read its value, otherwise just |
| 172 | + // swallow the header value. |
| 173 | + if (strcmp_P(recvbuffer, PSTR("Content-Length")) == 0) { |
| 174 | + len = (uint16_t)_client.parseInt(); |
| 175 | + } |
| 176 | + if (!_client.find("\r\n")) { |
| 177 | + DEBUG_PRINT(F("Failed to find end of header line!")); |
| 178 | + _client.stop(); |
| 179 | + return FeedData(); |
| 180 | + } |
| 181 | + } |
| 182 | + // If we didn't see a content-length header then assume a chunked transfer |
| 183 | + // encoding and read the length as the first line. |
| 184 | + if (len == 0) { |
| 185 | + len = (uint16_t)_client.parseInt(); |
| 186 | + if (!_client.find("\r\n")) { |
| 187 | + DEBUG_PRINT(F("Failed to find end of chunk size line!")); |
| 188 | + _client.stop(); |
| 189 | + return FeedData(); |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + // Let FeedData parse out the result. |
| 194 | + return FeedData(_client, len); |
| 195 | +} |
| 196 | + |
| 197 | +bool Adafruit_IO_Client::connected() { |
| 198 | + // Create connection to AIO service. Return true if successful and connection |
| 199 | + // is open, or false if something failed. |
| 200 | + |
| 201 | + // If connection is already open close it to start fresh. |
| 202 | + if (_client.connected()) { |
| 203 | + _client.stop(); |
| 204 | + } |
| 205 | + |
| 206 | + // Create connection. |
| 207 | + return _client.connect(_serviceHost, _servicePort) != 0; |
| 208 | +} |
| 209 | + |
| 210 | +void Adafruit_IO_Client::sendHeaders(const char* key) { |
| 211 | + // Send standard HTTP headers used by AIO REST requests. |
| 212 | + _client.print(F("Connection: close\r\n")); |
| 213 | + _client.print(F("User-Agent: Adafruit_IO_Client\r\n")); |
| 214 | + _client.print(F("Host: ")); |
| 215 | + _client.print(_serviceHost); |
| 216 | + _client.print(':'); |
| 217 | + _client.print(_servicePort, DEC); |
| 218 | + _client.print(F("\r\n")); |
| 219 | + _client.print(F("X-AIO-Key: ")); |
| 220 | + _client.print(key); |
| 221 | + _client.print(F("\r\n")); |
| 222 | +} |
0 commit comments