diff --git a/.gitignore b/.gitignore index 58e291ea..e23042dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ *.swo *.swp .DS_Store +.sync +.syntastic_cpp_config + +# platformio (testing) +.pioenvs +.piolibdeps +.travis.yml +platformio.ini + diff --git a/README.md b/README.md index 42b56da0..f7aa5694 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ This library requires the latest version of the [Arduino IDE](https://www.arduin * Latest version of the [Adafruit MQTT Library](https://github.com/adafruit/Adafruit_MQTT_Library) * Latest version of the [Arduino HTTP Client Library](https://github.com/arduino-libraries/ArduinoHttpClient) +### Adafruit Feather HUZZAH32 (ESP32) + +* Latest version of the [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32#using-through-arduino-ide) +* Latest version of the [Adafruit MQTT Library](https://github.com/adafruit/Adafruit_MQTT_Library) +* Latest version of the [Arduino HTTP Client Library](https://github.com/arduino-libraries/ArduinoHttpClient) + ### Adafruit Feather M0 WiFi with ATWINC1500 * Latest version of the [Arduino SAMD Arduino Core](https://github.com/arduino/ArduinoCore-samd) diff --git a/examples/adafruitio_01_subscribe/adafruitio_01_subscribe.ino b/examples/adafruitio_01_subscribe/adafruitio_01_subscribe.ino index dbaa982a..e486041c 100644 --- a/examples/adafruitio_01_subscribe/adafruitio_01_subscribe.ino +++ b/examples/adafruitio_01_subscribe/adafruitio_01_subscribe.ino @@ -32,7 +32,15 @@ void setup() { Serial.print("Connecting to Adafruit IO"); - // connect to io.adafruit.com + // because Adafruit IO doesn't support the MQTT + // retain flag right now, we need to load the + // last value for the "counter" feed manually and + // send it our handleMessage function + if (counter->exists()) { + handleMessage(counter->lastValue()); + } + + // start MQTT connection to io.adafruit.com io.connect(); // set up a message handler for the count feed. @@ -41,8 +49,11 @@ void setup() { // received from adafruit io. counter->onMessage(handleMessage); - // wait for a connection - while(io.status() < AIO_CONNECTED) { + // wait for an MQTT connection + // NOTE: when blending the HTTP and MQTT API, always use the mqttStatus + // method to check on MQTT connection status specifically + + while(io.mqttStatus() < AIO_CONNECTED) { Serial.print("."); delay(500); } diff --git a/library.properties b/library.properties index 9f88508f..293e5ebf 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Adafruit IO Arduino -version=2.7.0 +version=2.7.1 author=Adafruit maintainer=Adafruit sentence=Arduino library to access Adafruit IO. diff --git a/src/AdafruitIO.cpp b/src/AdafruitIO.cpp index e4673030..d2d3ba44 100644 --- a/src/AdafruitIO.cpp +++ b/src/AdafruitIO.cpp @@ -29,15 +29,17 @@ AdafruitIO::AdafruitIO(const char *user, const char *key) void errorCallback(char *err, uint16_t len) { - AIO_ERR_PRINTLN(); - AIO_ERR_PRINT("ERROR: "); - AIO_ERR_PRINTLN(err); - AIO_ERR_PRINTLN(); + AIO_ERROR_PRINTLN(); + AIO_ERROR_PRINT("ERROR: "); + AIO_ERROR_PRINTLN(err); + AIO_ERROR_PRINTLN(); } void AdafruitIO::connect() { + AIO_DEBUG_PRINTLN("AdafruitIO::connect()"); + if(_err_sub) { // setup error sub _err_sub = new Adafruit_MQTT_Subscribe(_mqtt, _err_topic); @@ -221,7 +223,11 @@ aio_status_t AdafruitIO::mqttStatus() // if the connection failed, // return so we don't hammer IO if(_status == AIO_CONNECT_FAILED) + { + AIO_ERROR_PRINT("mqttStatus() failed to connect"); + AIO_ERROR_PRINTLN(_mqtt->connectErrorString(_status)); return _status; + } if(_mqtt->connected()) return AIO_CONNECTED; diff --git a/src/AdafruitIO.h b/src/AdafruitIO.h index 7eaa99ec..5432d4d1 100644 --- a/src/AdafruitIO.h +++ b/src/AdafruitIO.h @@ -30,6 +30,7 @@ #error "This sketch requires Adafruit MQTT Library v0.17.0 or higher. Please install or upgrade using the Library Manager." #endif + class AdafruitIO { friend class AdafruitIO_Feed; diff --git a/src/AdafruitIO_Data.cpp b/src/AdafruitIO_Data.cpp index 67c83463..02ba40bb 100644 --- a/src/AdafruitIO_Data.cpp +++ b/src/AdafruitIO_Data.cpp @@ -4,7 +4,7 @@ // products from Adafruit! // // Copyright (c) 2015-2016 Adafruit Industries -// Authors: Tony DiCola, Todd Treece +// Authors: Tony DiCola, Todd Treece, Adam Bachman // Licensed under the MIT license. // // All text above must be included in any redistribution. @@ -53,6 +53,22 @@ AdafruitIO_Data::AdafruitIO_Data(AdafruitIO_Feed *f, char *csv) _parseCSV(); } +AdafruitIO_Data::AdafruitIO_Data(AdafruitIO_Feed *f, const char *csv) +{ + _lat = 0; + _lon = 0; + _ele = 0; + next_data = 0; + + memset(_feed, 0, AIO_FEED_NAME_LENGTH); + strcpy(_feed, f->name); + memset(_value, 0, AIO_DATA_LENGTH); + memset(_csv, 0, AIO_CSV_LENGTH); + strcpy(_csv, csv); + + _parseCSV(); +} + AdafruitIO_Data::AdafruitIO_Data(const char *f) { _lat = 0; @@ -83,6 +99,12 @@ AdafruitIO_Data::AdafruitIO_Data(const char *f, char *csv) bool AdafruitIO_Data::setCSV(char *csv) { + return setCSV((const char *)(csv)); +} + +bool AdafruitIO_Data::setCSV(const char *csv) +{ + memset(_csv, 0, AIO_CSV_LENGTH); strcpy(_csv, csv); return _parseCSV(); @@ -412,40 +434,229 @@ char* AdafruitIO_Data::charFromDouble(double d, int precision) return _double_buffer; } -bool AdafruitIO_Data::_parseCSV() +/* + * From the csv_parser project by semitrivial + * https://github.com/semitrivial/csv_parser/blob/93246cac509f85da12c6bb8c641fa75cd863c34f/csv.c - retrieved 2017-11-09 + * + * MIT License + * + * Copyright 2016 Samuel Alexander + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission 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. + * + */ + +static int count_fields( const char *line ) +{ + const char *ptr; + int cnt, fQuote; + + for ( cnt = 1, fQuote = 0, ptr = line; *ptr; ptr++ ) + { + if ( fQuote ) + { + if ( *ptr == '\"' ) + { + if ( ptr[1] == '\"' ) + { + ptr++; + continue; + } + fQuote = 0; + } + continue; + } + + switch( *ptr ) + { + case '\"': + fQuote = 1; + continue; + case ',': + cnt++; + continue; + default: + continue; + } + } + + if ( fQuote ) + { + return -1; + } + + return cnt; +} + +/* + * Given a string containing no linebreaks, or containing line breaks + * which are escaped by "double quotes", extract a NULL-terminated + * array of strings, one for every cell in the row. + */ +char **parse_csv( const char *line ) { - char *csv = _csv; - - if(csv[0] == '"') { - // handle quoted values - csv++; - int end = strstr(csv, "\",") - csv; - strncpy(_value, csv, end); - csv += (end + 2); - } else { - // handle normal values - strcpy(_value, strtok(csv, ",")); + char **buf, **bptr, *tmp, *tptr; + const char *ptr; + int fieldcnt, fQuote, fEnd; + + fieldcnt = count_fields( line ); + + if ( fieldcnt == -1 ) + { + return NULL; + } + + buf = (char **)malloc( sizeof(char*) * (fieldcnt+1) ); + + if ( !buf ) + { + return NULL; } - if (! _value) return false; + tmp = (char *)malloc( strlen(line) + 1 ); - // parse lat from csv and convert to float - char *lat = strtok(NULL, ","); - if (! lat) return false; + if ( !tmp ) + { + free( buf ); + return NULL; + } - _lat = atof(lat); + bptr = buf; + + for ( ptr = line, fQuote = 0, *tmp = '\0', tptr = tmp, fEnd = 0; ; ptr++ ) + { + if ( fQuote ) + { + if ( !*ptr ) + { + break; + } + + if ( *ptr == '\"' ) + { + if ( ptr[1] == '\"' ) + { + *tptr++ = '\"'; + ptr++; + continue; + } + fQuote = 0; + } + else { + *tptr++ = *ptr; + } + + continue; + } + + switch( *ptr ) + { + case '\"': + fQuote = 1; + continue; + case '\0': + fEnd = 1; + case ',': + *tptr = '\0'; + *bptr = strdup( tmp ); + + if ( !*bptr ) + { + for ( bptr--; bptr >= buf; bptr-- ) + { + free( *bptr ); + } + free( buf ); + free( tmp ); + + return NULL; + } + + bptr++; + tptr = tmp; + + if ( fEnd ) + { + break; + } else + { + continue; + } + + default: + *tptr++ = *ptr; + continue; + } + + if ( fEnd ) + { + break; + } + } - // parse lon from csv and convert to float - char *lon = strtok(NULL, ","); - if (! lon) return false; + *bptr = NULL; + free( tmp ); + return buf; +} - _lon = atof(lon); +//// END simple_csv SECTION - // parse ele from csv and convert to float - char *ele = strtok(NULL, ","); - if (! ele) return false; +bool AdafruitIO_Data::_parseCSV() +{ - _ele = atof(ele); + int field_count = count_fields(_csv); + + if (field_count > 0) + { + // this is normal IO data in `value,lat,lon,ele` format + char **fields = parse_csv(_csv); + + // first field is handled as string + strcpy(_value, fields[0]); + field_count--; + + // locations fields are handled with char * to float conversion + if (field_count > 0) + { + _lat = atof(fields[1]); + field_count--; + } + + if (field_count > 0) + { + _lon = atof(fields[1]); + field_count--; + } + + if (field_count > 0) + { + _ele = atof(fields[1]); + field_count--; + } + + return field_count == 0; + } + else + { + return false; + } return true; } + diff --git a/src/AdafruitIO_Data.h b/src/AdafruitIO_Data.h index f3094676..53447396 100644 --- a/src/AdafruitIO_Data.h +++ b/src/AdafruitIO_Data.h @@ -4,7 +4,7 @@ // products from Adafruit! // // Copyright (c) 2015-2016 Adafruit Industries -// Authors: Tony DiCola, Todd Treece +// Authors: Tony DiCola, Todd Treece, Adam Bachman // Licensed under the MIT license. // // All text above must be included in any redistribution. @@ -24,10 +24,12 @@ class AdafruitIO_Data { AdafruitIO_Data(); AdafruitIO_Data(AdafruitIO_Feed *f); AdafruitIO_Data(AdafruitIO_Feed *f, char *csv); + AdafruitIO_Data(AdafruitIO_Feed *f, const char *csv); AdafruitIO_Data(const char *f); AdafruitIO_Data(const char *f, char *csv); bool setCSV(char *csv); + bool setCSV(const char *csv); void setLocation(double lat, double lon, double ele=0); diff --git a/src/AdafruitIO_Definitions.h b/src/AdafruitIO_Definitions.h index e2c3cdee..998c5581 100644 --- a/src/AdafruitIO_Definitions.h +++ b/src/AdafruitIO_Definitions.h @@ -41,18 +41,30 @@ class AdafruitIOGroupCallback { }; +// Uncomment/comment to turn on/off debug output messages. +#define AIO_DEBUG // uncomment/comment to turn on/off error output #define AIO_ERROR // where debug messages will be printed #define AIO_PRINTER Serial +// If using something like Zero or Due, change the above to SerialUSB + +// Define actual debug output functions when necessary. +#ifdef AIO_DEBUG + #define AIO_DEBUG_PRINT(...) { AIO_PRINTER.print(__VA_ARGS__); } + #define AIO_DEBUG_PRINTLN(...) { AIO_PRINTER.println(__VA_ARGS__); } +#else + #define AIO_DEBUG_PRINT(...) {} + #define AIO_DEBUG_PRINTLN(...) {} +#endif #ifdef AIO_ERROR - #define AIO_ERR_PRINT(...) { AIO_PRINTER.print(__VA_ARGS__); } - #define AIO_ERR_PRINTLN(...) { AIO_PRINTER.println(__VA_ARGS__); } + #define AIO_ERROR_PRINT(...) { AIO_PRINTER.print(__VA_ARGS__); } + #define AIO_ERROR_PRINTLN(...) { AIO_PRINTER.println(__VA_ARGS__); } #else - #define AIO_ERR_PRINT(...) {} - #define AIO_ERR_PRINTLN(...) {} + #define AIO_ERROR_PRINT(...) {} + #define AIO_ERROR_PRINTLN(...) {} #endif #define AIO_PING_INTERVAL 60000 diff --git a/src/AdafruitIO_Feed.cpp b/src/AdafruitIO_Feed.cpp index 97bec404..042e2552 100644 --- a/src/AdafruitIO_Feed.cpp +++ b/src/AdafruitIO_Feed.cpp @@ -4,7 +4,7 @@ // products from Adafruit! // // Copyright (c) 2015-2016 Adafruit Industries -// Authors: Tony DiCola, Todd Treece +// Authors: Tony DiCola, Todd Treece, Adam Bachman // Licensed under the MIT license. // // All text above must be included in any redistribution. @@ -143,6 +143,44 @@ bool AdafruitIO_Feed::create() return status == 201; } +AdafruitIO_Data* AdafruitIO_Feed::lastValue() +{ + // 15 extra for api path, 12 for /data/retain, 1 for null + String url = "/api/v2/"; + url += _io->_username; + url += "/feeds/"; + url += name; + url += "/data/retain"; + + AIO_DEBUG_PRINT("lastValue get "); + AIO_DEBUG_PRINTLN(url); + + _io->_http->beginRequest(); + _io->_http->get(url.c_str()); + _io->_http->sendHeader("X-AIO-Key", _io->_key); + _io->_http->endRequest(); + + int status = _io->_http->responseStatusCode(); + String body = _io->_http->responseBody(); + + if (status >= 200 && status <= 299) { + + if (body.length() > 0) { + return new AdafruitIO_Data(this, body.c_str()); + } + + } else { + + AIO_ERROR_PRINT("error retrieving lastValue, status: "); + AIO_ERROR_PRINTLN(status); + AIO_ERROR_PRINT("response body: "); + AIO_ERROR_PRINTLN(_io->_http->responseBody()); + + return NULL; + + } +} + void AdafruitIO_Feed::setLocation(double lat, double lon, double ele) { data->setLocation(lat, lon, ele); diff --git a/src/AdafruitIO_Feed.h b/src/AdafruitIO_Feed.h index 70bdfb5e..17429b78 100644 --- a/src/AdafruitIO_Feed.h +++ b/src/AdafruitIO_Feed.h @@ -4,7 +4,7 @@ // products from Adafruit! // // Copyright (c) 2015-2016 Adafruit Industries -// Authors: Tony DiCola, Todd Treece +// Authors: Tony DiCola, Todd Treece, Adam Bachman // Licensed under the MIT license. // // All text above must be included in any redistribution. @@ -47,6 +47,7 @@ class AdafruitIO_Feed : public AdafruitIO_MQTT { const char *name; + AdafruitIO_Data *lastValue(); AdafruitIO_Data *data; private: