diff --git a/README.md b/README.md index d4dbd88..5e85d83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TwitterWebAPI -An Arduino library to talk to Twitter using [Twitter Web API](https://dev.twitter.com/overview/api) made for ESP8266. This is based on a sketch posted [here](https://github.com/soramimi/ESP8266Tweet). There are other approaches like using a bearer token [arduino-twitter-api](https://github.com/witnessmenow/arduino-twitter-api), but there are limitations in terms of not being able to send tweet. This can both search/read and post tweets. +An Arduino library to talk to Twitter using [Twitter Web API](https://dev.twitter.com/overview/api) made for ESP8266/ESP32. This is based on a sketch posted [here](https://github.com/soramimi/ESP8266Tweet). There are other approaches like using a bearer token [arduino-twitter-api](https://github.com/witnessmenow/arduino-twitter-api), but there are limitations in terms of not being able to send tweet. This can both search/read and post tweets. [![ESP8266 TwitterWebAPI](https://img.youtube.com/vi/ZDGhFYGj5tc/0.jpg)](https://www.youtube.com/watch?v=ZDGhFYGj5tc) @@ -16,13 +16,11 @@ Fill the obtained Consumer Key, Consumer Secret, Access Token and Access Token S ## Using the Library * Download this GitHub [library](https://github.com/debsahu/TwitterWebAPI/archive/master.zip). * In Arduino, Goto Sketch -> Include Library -> Add .ZIP Library... and point to the zip file downloaded. -* Install [TimeLib library](https://github.com/PaulStoffregen/Time) and [NTPClient library](https://github.com/arduino-libraries/NTPClient) using the same procedure. +* Install Optional [Arduino JSON](https://github.com/bblanchon/ArduinoJson) To use in your sketch include these lines. ``` #include -#include -#include ``` Define Twitter GET request timeout (optional, default is 1500ms) ``` @@ -44,13 +42,12 @@ You **WILL** need **Consumer Key, Consumer Secret, Access Token and Access Token ``` Declare clients before setup(). ``` -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, ntp_server, timezone*3600, 60000); // NTP server pool, offset (in seconds), update interval (in milliseconds) -TwitterClient tcr(timeClient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); +WiFiClientSecure sclient; +TwitterClient tcr(sclient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); ``` In setup make sure to start NTP connection. A correct time is required to be able to post/search on Twitter. ``` -tcr.startNTP(); +tcr.startNTP(ntp_server, timezone); ``` **Search for a key word** ``` @@ -68,11 +65,11 @@ std::string twitter_post_msg; tcr.tweet(twitter_post_msg); ``` ## Example: TwitterTweetSearchFSWiFiMgr -In addition to [TwitterWebAPI](https://github.com/debsahu/TwitterWebAPI), make sure that [TimeLib library](https://github.com/PaulStoffregen/Time) and [NTPClient library](https://github.com/arduino-libraries/NTPClient) is installed, and install the following libraries +In addition to [TwitterWebAPI](https://github.com/debsahu/TwitterWebAPI), ~~make sure that [TimeLib library](https://github.com/PaulStoffregen/Time) and [NTPClient library](https://github.com/arduino-libraries/NTPClient) is installed~~ (No longer needed) , and install the following libraries * [Arduino JSON](https://github.com/bblanchon/ArduinoJson) -* [WiFiManager](https://github.com/tzapu/WiFiManager) +* [WiFiManager - Development](https://github.com/tzapu/WiFiManager/tree/development) For display you can either install * [MAX7219 library](https://github.com/SensorsIot/MAX7219-4-digit-display-for-ESP8266) -or + or * [MD Parola](https://github.com/MajicDesigns/MD_Parola) & [MD_Max72xx](https://github.com/MajicDesigns/MD_MAX72xx) diff --git a/TwitterWebAPI.h b/TwitterWebAPI.h index c2e16e5..f593eff 100644 --- a/TwitterWebAPI.h +++ b/TwitterWebAPI.h @@ -1,29 +1,39 @@ +#ifdef ESP32 +#include +#elif defined(ESP8266) #include +#else +#error Platform not supported +#endif #include -#include -#include -#include +#include #include -#include +#include +// #include #include #include #include #include #ifndef TWI_TIMEOUT - #define TWI_TIMEOUT 1500 // in msec +#define TWI_TIMEOUT 1500 // in msec #endif -class misc { +class misc +{ private: static void url_encode_(const char *ptr, const char *end, std::vector *out) { - while (ptr < end) { + while (ptr < end) + { int c = (unsigned char)*ptr; ptr++; - if (isalnum(c) || strchr("_.-~", c)) { + if (isalnum(c) || strchr("_.-~", c)) + { print(out, c); - } else { + } + else + { char tmp[10]; sprintf(tmp, "%%%02X", c); print(out, tmp[0]); @@ -34,12 +44,16 @@ class misc { } static void url_decode_(const char *ptr, const char *end, std::vector *out) { - while (ptr < end) { + while (ptr < end) + { int c = (unsigned char)*ptr; ptr++; - if (c == '+') { + if (c == '+') + { c = ' '; - } else if (c == '%' && isxdigit((unsigned char)ptr[0]) && isxdigit((unsigned char)ptr[1])) { + } + else if (c == '%' && isxdigit((unsigned char)ptr[0]) && isxdigit((unsigned char)ptr[1])) + { char tmp[3]; // '%XX' tmp[0] = ptr[0]; tmp[1] = ptr[1]; @@ -50,6 +64,7 @@ class misc { print(out, c); } } + public: static void print(std::vector *out, char c) { @@ -73,7 +88,8 @@ class misc { } static std::string to_stdstr(std::vector const &vec) { - if (!vec.empty()) { + if (!vec.empty()) + { char const *begin = &vec.at(0); char const *end = begin + vec.size(); return std::string(begin, end); @@ -83,7 +99,8 @@ class misc { static std::string url_encode(char const *str, char const *end) { - if (!str) { + if (!str) + { return std::string(); } @@ -96,7 +113,8 @@ class misc { } static std::string url_decode(char const *str, char const *end) { - if (!str) { + if (!str) + { return std::string(); } @@ -132,15 +150,20 @@ class misc { char const *end = begin + str.size(); char const *ptr = begin; - while (ptr < end) { + while (ptr < end) + { int c = (unsigned char)*ptr; - if (isalnum(c) || strchr("_.-~", c)) { - } else { + if (isalnum(c) || strchr("_.-~", c)) + { + } + else + { break; } ptr++; } - if (ptr == end) { + if (ptr == end) + { return str; } @@ -158,18 +181,20 @@ class misc { char const *end = begin + str.size(); char const *ptr = begin; - while (ptr < end) { + while (ptr < end) + { int c = *ptr & 0xff; - if (c == '+' || c == '%') { + if (c == '+' || c == '%') + { break; } ptr++; } - if (ptr == end) { + if (ptr == end) + { return str; } - std::vector out; out.reserve(str.size() + 10); @@ -178,36 +203,217 @@ class misc { return to_stdstr(out); } - }; -class base64 { +class base64 +{ private: static const unsigned char PAD = '='; static unsigned char enc(int c) { static const unsigned char _encode_table[] = { - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, - 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, - 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, - 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f, + 0x41, + 0x42, + 0x43, + 0x44, + 0x45, + 0x46, + 0x47, + 0x48, + 0x49, + 0x4a, + 0x4b, + 0x4c, + 0x4d, + 0x4e, + 0x4f, + 0x50, + 0x51, + 0x52, + 0x53, + 0x54, + 0x55, + 0x56, + 0x57, + 0x58, + 0x59, + 0x5a, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x30, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x2b, + 0x2f, }; return _encode_table[c & 63]; } static unsigned char dec(int c) { static const unsigned char _decode_table[] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x3e, + 0xff, + 0xff, + 0xff, + 0x3f, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x3a, + 0x3b, + 0x3c, + 0x3d, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10, + 0x11, + 0x12, + 0x13, + 0x14, + 0x15, + 0x16, + 0x17, + 0x18, + 0x19, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x20, + 0x21, + 0x22, + 0x23, + 0x24, + 0x25, + 0x26, + 0x27, + 0x28, + 0x29, + 0x2a, + 0x2b, + 0x2c, + 0x2d, + 0x2e, + 0x2f, + 0x30, + 0x31, + 0x32, + 0x33, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, }; return _decode_table[c & 127]; } + public: static void encode(char const *src, size_t length, std::vector *out) { @@ -215,23 +421,31 @@ class base64 { dstlen = (length + 2) / 3 * 4; out->resize(dstlen); - if (dstlen == 0) { + if (dstlen == 0) + { return; } char *dst = &out->at(0); dstpos = 0; - for (srcpos = 0; srcpos < length; srcpos += 3) { + for (srcpos = 0; srcpos < length; srcpos += 3) + { int v = (unsigned char)src[srcpos] << 16; - if (srcpos + 1 < length) { + if (srcpos + 1 < length) + { v |= (unsigned char)src[srcpos + 1] << 8; - if (srcpos + 2 < length) { + if (srcpos + 2 < length) + { v |= (unsigned char)src[srcpos + 2]; dst[dstpos + 3] = enc(v); - } else { + } + else + { dst[dstpos + 3] = PAD; } dst[dstpos + 2] = enc(v >> 6); - } else { + } + else + { dst[dstpos + 2] = PAD; dst[dstpos + 3] = PAD; } @@ -249,36 +463,50 @@ class base64 { out->reserve(length * 3 / 4); int count = 0; int bits = 0; - while (1) { - if (isspace(*ptr)) { + while (1) + { + if (isspace(*ptr)) + { ptr++; - } else { + } + else + { unsigned char c = 0xff; - if (ptr < end && *ptr < 0x80) { + if (ptr < end && *ptr < 0x80) + { c = dec(*ptr); } - if (c < 0x40) { + if (c < 0x40) + { bits = (bits << 6) | c; count++; - } else { - if (count < 4) { + } + else + { + if (count < 4) + { bits <<= (4 - count) * 6; } c = 0xff; } - if (count == 4 || c == 0xff) { - if (count >= 2) { + if (count == 4 || c == 0xff) + { + if (count >= 2) + { out->push_back(bits >> 16); - if (count >= 3) { + if (count >= 3) + { out->push_back(bits >> 8); - if (count == 4) { + if (count == 4) + { out->push_back(bits); } } } count = 0; bits = 0; - if (c == 0xff) { + if (c == 0xff) + { break; } } @@ -304,7 +532,8 @@ class base64 { } static inline std::string to_s_(std::vector const *vec) { - if (!vec || vec->empty()) return std::string(); + if (!vec || vec->empty()) + return std::string(); return std::string((char const *)&(*vec)[0], vec->size()); } static inline std::string encode(std::string const &src) @@ -319,52 +548,61 @@ class base64 { decode((char const *)src.c_str(), src.size(), &vec); return to_s_(&vec); } - }; -class sha1 { +class sha1 +{ public: - enum { + enum + { Success = 0, - Null, /* Null pointer parameter */ - InputTooLong, /* input data too long */ - StateError /* called Input after Result */ + Null, /* Null pointer parameter */ + InputTooLong, /* input data too long */ + StateError /* called Input after Result */ }; + public: static const int HashSize = 20; - struct Context { - uint32_t Intermediate_Hash[HashSize/4]; /* Message Digest */ + struct Context + { + uint32_t Intermediate_Hash[HashSize / 4]; /* Message Digest */ - uint32_t Length_Low; /* Message length in bits */ - uint32_t Length_High; /* Message length in bits */ + uint32_t Length_Low; /* Message length in bits */ + uint32_t Length_High; /* Message length in bits */ /* Index into message block array */ int_least16_t Message_Block_Index; - uint8_t Message_Block[64]; /* 512-bit message blocks */ + uint8_t Message_Block[64]; /* 512-bit message blocks */ - int Computed; /* Is the digest computed? */ - int Corrupted; /* Is the message digest corrupted? */ + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corrupted? */ }; static uint32_t CircularShift(uint32_t bits, uint32_t word) { - return ((word) << (bits)) | ((word) >> (32-(bits))); + return ((word) << (bits)) | ((word) >> (32 - (bits))); } static void PadMessage(Context *context) { - if (context->Message_Block_Index > 55) { + if (context->Message_Block_Index > 55) + { context->Message_Block[context->Message_Block_Index++] = 0x80; - while (context->Message_Block_Index < 64) { + while (context->Message_Block_Index < 64) + { context->Message_Block[context->Message_Block_Index++] = 0; } ProcessMessageBlock(context); - while (context->Message_Block_Index < 56) { + while (context->Message_Block_Index < 56) + { context->Message_Block[context->Message_Block_Index++] = 0; } - } else { + } + else + { context->Message_Block[context->Message_Block_Index++] = 0x80; - while(context->Message_Block_Index < 56) { + while (context->Message_Block_Index < 56) + { context->Message_Block[context->Message_Block_Index++] = 0; } } @@ -383,25 +621,26 @@ class sha1 { static void ProcessMessageBlock(Context *context) { const uint32_t K[] = { - 0x5A827999, - 0x6ED9EBA1, - 0x8F1BBCDC, - 0xCA62C1D6 - }; + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6}; int t; uint32_t temp; uint32_t W[80]; uint32_t A, B, C, D, E; - for (t = 0; t < 16; t++) { + for (t = 0; t < 16; t++) + { W[t] = context->Message_Block[t * 4] << 24; W[t] |= context->Message_Block[t * 4 + 1] << 16; W[t] |= context->Message_Block[t * 4 + 2] << 8; W[t] |= context->Message_Block[t * 4 + 3]; } - for (t = 16; t < 80; t++) { - W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + for (t = 16; t < 80; t++) + { + W[t] = CircularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); } A = context->Intermediate_Hash[0]; @@ -410,38 +649,42 @@ class sha1 { D = context->Intermediate_Hash[3]; E = context->Intermediate_Hash[4]; - for (t = 0; t < 20; t++) { - temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + for (t = 0; t < 20; t++) + { + temp = CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; E = D; D = C; - C = CircularShift(30,B); + C = CircularShift(30, B); B = A; A = temp; } - for (t = 20; t < 40; t++) { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + for (t = 20; t < 40; t++) + { + temp = CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1]; E = D; D = C; - C = CircularShift(30,B); + C = CircularShift(30, B); B = A; A = temp; } - for (t = 40; t < 60; t++) { - temp = CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + for (t = 40; t < 60; t++) + { + temp = CircularShift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; E = D; D = C; - C = CircularShift(30,B); + C = CircularShift(30, B); B = A; A = temp; } - for (t = 60; t < 80; t++) { - temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + for (t = 60; t < 80; t++) + { + temp = CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3]; E = D; D = C; - C = CircularShift(30,B); + C = CircularShift(30, B); B = A; A = temp; } @@ -454,6 +697,7 @@ class sha1 { context->Message_Block_Index = 0; } + public: static int Reset(Context *context) { @@ -462,18 +706,18 @@ class sha1 { return Null; } - context->Length_Low = 0; - context->Length_High = 0; - context->Message_Block_Index = 0; + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; - context->Intermediate_Hash[0] = 0x67452301; - context->Intermediate_Hash[1] = 0xEFCDAB89; - context->Intermediate_Hash[2] = 0x98BADCFE; - context->Intermediate_Hash[3] = 0x10325476; - context->Intermediate_Hash[4] = 0xC3D2E1F0; + context->Intermediate_Hash[0] = 0x67452301; + context->Intermediate_Hash[1] = 0xEFCDAB89; + context->Intermediate_Hash[2] = 0x98BADCFE; + context->Intermediate_Hash[3] = 0x10325476; + context->Intermediate_Hash[4] = 0xC3D2E1F0; - context->Computed = 0; - context->Corrupted = 0; + context->Computed = 0; + context->Corrupted = 0; return Success; } @@ -481,63 +725,75 @@ class sha1 { { int i; - if (!context || !Message_Digest) { + if (!context || !Message_Digest) + { return Null; } - if (context->Corrupted) { + if (context->Corrupted) + { return context->Corrupted; } - if (!context->Computed) { + if (!context->Computed) + { PadMessage(context); - for (i = 0; i < 64; ++i) { + for (i = 0; i < 64; ++i) + { /* message may be sensitive, clear it out */ context->Message_Block[i] = 0; } - context->Length_Low = 0; /* and clear length */ + context->Length_Low = 0; /* and clear length */ context->Length_High = 0; context->Computed = 1; } for (i = 0; i < HashSize; ++i) { - Message_Digest[i] = context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ); + Message_Digest[i] = context->Intermediate_Hash[i >> 2] >> 8 * (3 - (i & 0x03)); } return Success; } static int Input(Context *context, const uint8_t *message_array, unsigned int length) { - if (!length) { + if (!length) + { return Success; } - if (!context || !message_array) { + if (!context || !message_array) + { return Null; } - if (context->Computed) { + if (context->Computed) + { context->Corrupted = StateError; return StateError; } - if (context->Corrupted) { + if (context->Corrupted) + { return context->Corrupted; } - while(length-- && !context->Corrupted) { + while (length-- && !context->Corrupted) + { context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF); context->Length_Low += 8; - if (context->Length_Low == 0) { + if (context->Length_Low == 0) + { context->Length_High++; - if (context->Length_High == 0) { + if (context->Length_High == 0) + { /* Message is too long */ context->Corrupted = 1; } } - if (context->Message_Block_Index == 64) { + if (context->Message_Block_Index == 64) + { ProcessMessageBlock(context); } @@ -548,9 +804,11 @@ class sha1 { } }; -class oauth { +class oauth +{ public: - class Keys { + class Keys + { public: std::string consumer_key; std::string consumer_sec; @@ -562,23 +820,22 @@ class oauth { } Keys(std::string consumer_key, std::string consumer_sec, std::string accesstoken, std::string accesstoken_sec) - : consumer_key(consumer_key) - , consumer_sec(consumer_sec) - , accesstoken(accesstoken) - , accesstoken_sec(accesstoken_sec) + : consumer_key(consumer_key), consumer_sec(consumer_sec), accesstoken(accesstoken), accesstoken_sec(accesstoken_sec) { } }; - enum http_method_t { + enum http_method_t + { GET, POST, }; - struct Request { + struct Request + { std::string url; std::string post; std::string getdata; std::string right; -// time_t timevalue; + // time_t timevalue; }; static Request sign(const char *url, http_method_t http_method, Keys const &keys, time_t currentTime) { @@ -587,32 +844,37 @@ class oauth { process_(&vec, http_method, keys, currentTime); - if (http_method == POST) { + if (http_method == POST) + { Request req; -// req.timevalue = currentTime; + // req.timevalue = currentTime; req.post = oauth::build_url(vec, 1); req.url = vec.at(0); return req; - } else if (http_method == GET) { + } + else if (http_method == GET) + { Request req; -// req.timevalue = currentTime; + // req.timevalue = currentTime; req.getdata = oauth::build_oauth_auth(vec, 1); req.right = oauth::split_url_right(url); req.url = vec.at(0); return req; - } else { + } + else + { Request req; req.url = oauth::build_url(vec, 0); return req; } } + private: static std::string encode_base64(const unsigned char *src, int size) { std::vector vec; base64::encode((char const *)src, size, &vec); return misc::to_stdstr(vec); - } static std::string decode_base64(const char *src) { @@ -630,31 +892,33 @@ class oauth { } static void process_(std::vector *args, http_method_t http_method, const Keys &keys, time_t timevalue) { - auto to_s = [](int v)->std::string{ + auto to_s = [](int v) -> std::string { char tmp[100]; sprintf(tmp, "%d", v); return tmp; }; { - auto nonce = [](time_t timevalue){ + auto nonce = [](time_t timevalue) { static const char *chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; const unsigned int max = 26 + 26 + 10 + 1; char tmp[50]; srand((unsigned int)timevalue); //int len = 15 + rand() % 16; - int len=42; // New twitter API requires 42 characters - for (int i = 0; i < len; i++) { + int len = 42; // New twitter API requires 42 characters + for (int i = 0; i < len; i++) + { tmp[i] = chars[rand() % max]; } return std::string(tmp, len); }; - auto is_key_contains = [](std::vector const &argv, std::string const &key) - { + auto is_key_contains = [](std::vector const &argv, std::string const &key) { size_t keylen = key.size(); - for (std::string const &s : argv) { - if (strncmp(s.c_str(), key.c_str(), keylen) == 0 && s[keylen] == '=') { + for (std::string const &s : argv) + { + if (strncmp(s.c_str(), key.c_str(), keylen) == 0 && s[keylen] == '=') + { return true; } } @@ -662,20 +926,23 @@ class oauth { }; std::string oauth_nonce = "oauth_nonce"; - if (!is_key_contains(*args, oauth_nonce)) { + if (!is_key_contains(*args, oauth_nonce)) + { oauth_nonce += '='; oauth_nonce += nonce(timevalue); args->push_back(oauth_nonce); } std::string oauth_timestamp = "oauth_timestamp"; - if (!is_key_contains(*args, oauth_timestamp)) { + if (!is_key_contains(*args, oauth_timestamp)) + { oauth_timestamp += '='; oauth_timestamp += to_s((int)timevalue); args->push_back(oauth_timestamp); } - if (!keys.accesstoken.empty()) { + if (!keys.accesstoken.empty()) + { std::string oauth_token = "oauth_token"; oauth_token += '='; oauth_token += keys.accesstoken; @@ -686,27 +953,30 @@ class oauth { oauth_consumer_key += '='; oauth_consumer_key += keys.consumer_key; args->push_back(oauth_consumer_key); - + std::string oauth_signature_method = "oauth_signature_method"; oauth_signature_method += '='; oauth_signature_method += "HMAC-SHA1"; args->push_back(oauth_signature_method); std::string oauth_version = "oauth_version"; - if (!is_key_contains(*args, oauth_version)) { + if (!is_key_contains(*args, oauth_version)) + { oauth_version += '='; oauth_version += "1.0"; args->push_back(oauth_version); } - } std::sort(args->begin() + 1, args->end()); - - auto Combine = [](std::initializer_list list){ + + auto Combine = [](std::initializer_list list) { std::string text; - for (std::string const &s : list) { - if (!s.empty()) { - if (!text.empty()) { + for (std::string const &s : list) + { + if (!s.empty()) + { + if (!text.empty()) + { text += '&'; } text += misc::url_encode(s); @@ -718,9 +988,12 @@ class oauth { std::string query = oauth::build_url(*args, 1); std::string httpmethod; - if (http_method == http_method_t::GET) { + if (http_method == http_method_t::GET) + { httpmethod = "GET"; - } else if (http_method == http_method_t::POST) { + } + else if (http_method == http_method_t::POST) + { httpmethod = "POST"; } std::string m = Combine({httpmethod, (*args)[0], query}); @@ -737,20 +1010,27 @@ class oauth { out->clear(); char const *left = url; char const *ptr = left; - while (1) { + while (1) + { int c = *ptr & 0xff; - if (c == '&' || c == '?' || c == 0) { - if (left < ptr) { + if (c == '&' || c == '?' || c == 0) + { + if (left < ptr) + { out->push_back(std::string(left, ptr)); } - if (c == 0) break; + if (c == 0) + break; ptr++; left = ptr; - } else { + } + else + { ptr++; } } - for (size_t i = 1; i < out->size(); i++) { + for (size_t i = 1; i < out->size(); i++) + { std::string *p = &out->at(i); *p = misc::url_decode(*p); } @@ -758,27 +1038,33 @@ class oauth { static std::string split_url_right(const char *url) { std::string u = std::string(url); - std::string rightstr = u.substr(u.find("?"),u.length()); + std::string rightstr = u.substr(u.find("?"), u.length()); return rightstr; } static std::string build_url(const std::vector &argv, int start) { const char sep = '&'; std::string query; - for (size_t i = start; i < argv.size(); i++) { + for (size_t i = start; i < argv.size(); i++) + { std::string s = argv[i]; - if (i > 0) { + if (i > 0) + { char const *p = s.c_str(); char const *e = strchr(p, '='); - if (e) { + if (e) + { std::string name(p, e); std::string value = e + 1; s = name + '=' + misc::url_encode(value); - } else { + } + else + { s += '='; } } - if (!query.empty()) { + if (!query.empty()) + { query += sep; } query += s; @@ -792,55 +1078,66 @@ class oauth { const char sep = ','; bool flag = false; std::string query; - for (size_t i = start; i < argv.size(); i++) { + for (size_t i = start; i < argv.size(); i++) + { std::string s = argv[i]; - if (i > 0) { + if (i > 0) + { char const *p = s.c_str(); char const *e = strchr(p, '='); - if (e) { + if (e) + { std::string name(p, e); std::string value = e + 1; //s = misc::url_encode(name) + '=' + misc::url_encode(value); - if ((name.compare("oauth_signature") == 0) or (name.compare("oauth_consumer_key") == 0) or (name.compare("oauth_nonce") == 0) or (name.compare("oauth_signature_method") == 0) or (name.compare("oauth_timestamp") == 0) or (name.compare("oauth_token") == 0) or (name.compare("oauth_version") == 0)) { + if ((name.compare("oauth_signature") == 0) or (name.compare("oauth_consumer_key") == 0) or (name.compare("oauth_nonce") == 0) or (name.compare("oauth_signature_method") == 0) or (name.compare("oauth_timestamp") == 0) or (name.compare("oauth_token") == 0) or (name.compare("oauth_version") == 0)) + { s = " " + misc::url_encode(name) + '=' + '"' + misc::url_encode(value) + '"'; flag = true; - } else { + } + else + { flag = false; } - } else { + } + else + { s += '='; } } - if (!query.empty() and flag) { + if (!query.empty() and flag) + { query += sep; } - if (flag) query += s; + if (flag) + query += s; } return query; } - + static void hmac_sha1(uint8_t const *key, size_t keylen, uint8_t const *in, size_t inlen, uint8_t *out) { sha1::Context sha1; uint8_t tmp[20]; - + uint8_t ibuf[64]; uint8_t obuf[64]; memset(ibuf, 0, 64); memset(obuf, 0, 64); memcpy(ibuf, key, keylen); memcpy(obuf, key, keylen); - - for (int i = 0; i < 64; i++) { + + for (int i = 0; i < 64; i++) + { ibuf[i] ^= 0x36; obuf[i] ^= 0x5c; } - + sha1::Reset(&sha1); sha1::Input(&sha1, ibuf, 64); sha1::Input(&sha1, in, inlen); sha1::Result(&sha1, tmp); - + sha1::Reset(&sha1); sha1::Input(&sha1, obuf, 64); sha1::Input(&sha1, tmp, 20); @@ -850,12 +1147,12 @@ class oauth { { uint8_t key[20]; uint8_t result[20]; - + sha1::Context sha1; sha1::Reset(&sha1); sha1::Input(&sha1, (uint8_t const *)k.c_str(), k.size()); sha1::Result(&sha1, key); - + hmac_sha1(key, 20, (uint8_t const *)m.c_str(), m.size(), result); std::vector vec; base64::encode((char const *)result, 20, &vec); @@ -863,12 +1160,14 @@ class oauth { } }; -class URL { +class URL +{ private: std::string scheme_; std::string host_; int port_ = 0; std::string path_; + public: URL(const std::string &url) { @@ -877,38 +1176,52 @@ class URL { char const *right; left = str; right = strstr(left, "://"); - if (right) { + if (right) + { scheme_.assign(str, right - str); left = right + 3; } right = strchr(left, '/'); - if (right) { + if (right) + { char const *p = strchr(left, ':'); - if (p && left < p && p < right) { + if (p && left < p && p < right) + { int n = 0; char const *q = p + 1; - while (q < right) { - if (isdigit(*q & 0xff)) { + while (q < right) + { + if (isdigit(*q & 0xff)) + { n = n * 10 + (*q - '0'); - } else { + } + else + { n = -1; break; } q++; } host_.assign(left, p - left); - if (n > 0 && n < 65536) { + if (n > 0 && n < 65536) + { port_ = n; } - } else { + } + else + { host_.assign(left, right - left); } path_ = right; } - if (port_ == 0) { - if (scheme_ == "http") { + if (port_ == 0) + { + if (scheme_ == "http") + { port_ = 80; - } else if (scheme_ == "https") { + } + else if (scheme_ == "https") + { port_ = 443; } } @@ -919,25 +1232,34 @@ class URL { std::string const &path() const { return path_; } bool isssl() const { - if (scheme() == "https") return true; - if (scheme() == "http") return false; - if (port() == 443) return true; + if (scheme() == "https") + return true; + if (scheme() == "http") + return false; + if (port() == 443) + return true; return false; } }; -class TwitterClient { +class TwitterClient +{ private: - //WiFiClientSecure *client; - NTPClient *timeClient; - struct Data { + WiFiClientSecure *client; + timeval _cbtime; + time_t _now; + struct Data + { oauth::Keys keys; } data; - oauth::Keys const &keys() const { + oauth::Keys const &keys() const + { return data.keys; } - struct RequestOption { - enum Method { + struct RequestOption + { + enum Method + { GET, POST, }; @@ -955,7 +1277,7 @@ class TwitterClient { post_end = end; method = POST; } - void set_get_data(char const *begin, char const *end, char const * right) + void set_get_data(char const *begin, char const *end, char const *right) { get_begin = begin; get_end = end; @@ -972,60 +1294,67 @@ class TwitterClient { }; bool request(const std::string &url, RequestOption const &opt, String *reply) { - if (reply) *reply = ""; - if (opt.method == RequestOption::GET) { - if (opt.upload_path.empty()) { - String host, path; - int port = 0; - { + if (reply) + *reply = ""; + if (opt.method == RequestOption::GET) + { + if (opt.upload_path.empty()) + { + String host, path; + int port = 0; + { URL l(url); host = l.host().c_str(); path = l.path().c_str(); port = l.port(); + } + //WiFiClientSecure client; + client->setInsecure(); + if (client->connected()) + { + client->flush(); + client->stop(); + } + client->setTimeout(TWI_TIMEOUT); + if (client->connect(host.c_str(), port)) + { + client->print("GET " + path); + client->print(opt.right_str.c_str()); + client->println(" HTTP/1.1"); + client->println("Host: " + host); + client->println("User-Agent: ESP8266"); + client->println("Accept: */*"); + client->println("Connection: close"); + client->println("Content-Type: application/x-www-form-urlencoded;"); + client->print("Authorization: OAuth"); + client->println(opt.get_begin); + client->println(""); + + while (client->connected()) + { + String header = client->readStringUntil('\n'); + if (header == "\r") + break; // headers received } - WiFiClientSecure client; - client.setInsecure(); - if (client.connected()) { client.flush(); client.stop(); } - client.setTimeout(TWI_TIMEOUT); - if (client.connect(host.c_str(), port)) { -// Serial.print("GET " + path ); -// Serial.print(opt.right_str.c_str()); -// Serial.println(" HTTP/1.1"); -// Serial.println("Host: " + host); -// Serial.println("User-Agent: ESP8266"); -// Serial.println("Accept: */*"); -// Serial.println("Connection: close"); -// Serial.println("Content-Type: application/x-www-form-urlencoded;"); -// Serial.print("Authorization: OAuth"); -// Serial.println(opt.get_begin); -// Serial.println(""); - - client.print("GET " + path ); - client.print(opt.right_str.c_str()); - client.println(" HTTP/1.1"); - client.println("Host: " + host); - client.println("User-Agent: ESP8266"); - client.println("Accept: */*"); - client.println("Connection: close"); - client.println("Content-Type: application/x-www-form-urlencoded;"); - client.print("Authorization: OAuth"); - client.println(opt.get_begin); - client.println(""); - - while (client.connected()) { - String header = client.readStringUntil('\n'); - if (header == "\r") break; // headers received + *reply = client->readString(); + //String body = client->readString(); + //*reply = body; + if (client->available()) + { + client->flush(); + client->stop(); } - *reply = client.readString(); - //String body = client.readString(); - //*reply = body; - if (client.available()) { client.flush(); client.stop(); } return true; } } - } else if (opt.method == RequestOption::POST) { - if (reply) *reply = "";; - if (opt.upload_path.empty()) { + } + else if (opt.method == RequestOption::POST) + { + if (reply) + *reply = ""; + ; + if (opt.upload_path.empty()) + { String host, path; int port = 0; { @@ -1034,78 +1363,118 @@ class TwitterClient { path = l.path().c_str(); port = l.port(); } - WiFiClientSecure client; - client.setInsecure(); - if (client.connected()) { client.flush(); client.stop(); } - client.setTimeout(TWI_TIMEOUT); - if (client.connect(host.c_str(), port)) { + client->setInsecure(); + if (client->connected()) + { + client->flush(); + client->stop(); + } + client->setTimeout(TWI_TIMEOUT); + if (client->connect(host.c_str(), port)) + { int len = opt.post_end - opt.post_begin; - client.println("POST " + path + " HTTP/1.1"); - client.println("Host: " + host); - client.println("User-Agent: ESP8266"); - client.println("Connection: close"); - client.println("Content-Type: application/x-www-form-urlencoded;"); - client.print("Content-Length: "); - client.println(len); - client.println(); - client.write((uint8_t const *)opt.post_begin, len); - String s = client.readString(); + client->println("POST " + path + " HTTP/1.1"); + client->println("Host: " + host); + client->println("User-Agent: ESP8266"); + client->println("Connection: close"); + client->println("Content-Type: application/x-www-form-urlencoded;"); + client->print("Content-Length: "); + client->println(len); + client->println(); + client->write((uint8_t const *)opt.post_begin, len); + String s = client->readString(); *reply = s; //Serial.println("Tweeted"); - if (client.available()) { client.flush(); client.stop(); } + if (client->available()) + { + client->flush(); + client->stop(); + } return true; } - } else { + } + else + { // not implemented } } return false; } + public: TwitterClient() { } - TwitterClient(NTPClient &timeClient, std::string const &consumer_key, std::string const &consumer_sec, std::string const &accesstoken, std::string const &accesstoken_sec) + TwitterClient(WiFiClientSecure &client, std::string const &consumer_key, std::string const &consumer_sec, std::string const &accesstoken, std::string const &accesstoken_sec) { - //this->client = &client; - this->timeClient = &timeClient; + this->client = &client; data.keys.consumer_key = consumer_key; data.keys.consumer_sec = consumer_sec; data.keys.accesstoken = accesstoken; data.keys.accesstoken_sec = accesstoken_sec; } - void startNTP() + void getNTPTime() + { + // Serial.print("Setting time using SNTP"); + time_t now = time(nullptr); + time_t nowish = 1510592825; + while (now < nowish) + { + delay(500); + //Serial.print("."); + now = time(nullptr); + } + // Serial.println("done!"); + struct tm timeinfo; + gmtime_r(&now, &timeinfo); + // Serial.print("Current time: "); + // Serial.print(asctime(&timeinfo)); + } + + void startNTP() + { + gettimeofday(&_cbtime, NULL); + configTime(0, 0, "pool.ntp.org"); + getNTPTime(); + } + + void startNTP(uint8_t TIME_ZONE) + { + gettimeofday(&_cbtime, NULL); + configTime(TIME_ZONE * 3600, 0, "pool.ntp.org"); + getNTPTime(); + getNTPTime(); + } + + void startNTP(uint8_t TIME_ZONE, uint32_t TIME_ZONE_OFFSET) { - //client->setTimeout(TWI_TIMEOUT); - timeClient->begin(); - timeClient->forceUpdate(); + gettimeofday(&_cbtime, NULL); + configTime(TIME_ZONE * 3600, TIME_ZONE_OFFSET, "pool.ntp.org"); + getNTPTime(); } - - void startNTP(int timeOffset) + + void startNTP(const char *NTP_SERVER, uint8_t TIME_ZONE) { - //client->setTimeout(TWI_TIMEOUT); - timeClient->setTimeOffset(timeOffset); - timeClient->begin(); - timeClient->forceUpdate(); + gettimeofday(&_cbtime, NULL); + configTime(TIME_ZONE * 3600, 0, NTP_SERVER); + getNTPTime(); } - - void startNTP(int timeOffset, int updateInterval) - { - //client->setTimeout(TWI_TIMEOUT); - timeClient->setTimeOffset(timeOffset); - timeClient->setUpdateInterval(updateInterval); - timeClient->begin(); - timeClient->forceUpdate(); + + void startNTP(const char *NTP_SERVER, uint8_t TIME_ZONE, uint32_t TIME_ZONE_OFFSET) + { + gettimeofday(&_cbtime, NULL); + configTime(TIME_ZONE * 3600, TIME_ZONE_OFFSET, NTP_SERVER); + getNTPTime(); } - + bool tweet(std::string message, const std::vector *media_ids = nullptr) { - timeClient->update(); - time_t currentTime = (time_t) timeClient->getEpochTime(); -// Serial.print("Epoch: "); Serial.println(currentTime); - - if (message.empty()) { + gettimeofday(&_cbtime, NULL); + _now = time(nullptr); + time_t currentTime = _now; + if (message.empty()) + { return false; } @@ -1113,17 +1482,22 @@ class TwitterClient { url += "?status="; url += misc::url_encode(message); - if (media_ids && !media_ids->empty()) { + if (media_ids && !media_ids->empty()) + { std::string ids; - for (std::string const &media_id : *media_ids) { - if (!media_id.empty()) { - if (!ids.empty()) { + for (std::string const &media_id : *media_ids) + { + if (!media_id.empty()) + { + if (!ids.empty()) + { ids += ','; } ids += media_id; } } - if (!ids.empty()) { + if (!ids.empty()) + { url += "&media_ids="; url += ids; } @@ -1137,21 +1511,26 @@ class TwitterClient { return request(oauth_req.url, opt, &res); } - String searchTwitter(std::string message) + String searchTwitter(std::string message, bool extended = false) { - timeClient->update(); - time_t currentTime = (time_t) timeClient->getEpochTime(); - //Serial.print("Epoch: "); Serial.println(currentTime); - - if (message.empty()) { + gettimeofday(&_cbtime, NULL); + _now = time(nullptr); + time_t currentTime = _now; + Serial.print("Epoch: "); + Serial.println(currentTime); + + if (message.empty()) + { return "Error with user search term!"; } std::string url = "https://api.twitter.com/1.1/search/tweets.json"; - + url += "?q="; url += misc::url_encode(message); url += "&result_type=recent&count=1"; + if (extended) + url += "&tweet_mode=extended"; oauth::Request oauth_req = oauth::sign(url.c_str(), oauth::GET, keys(), currentTime); String res; @@ -1159,28 +1538,34 @@ class TwitterClient { char const *p = oauth_req.getdata.c_str(); char const *r = oauth_req.right.c_str(); opt.set_get_data(p, p + oauth_req.getdata.size(), r); - if(request(oauth_req.url, opt, &res)){ + if (request(oauth_req.url, opt, &res)) + { return res; - } else { + } + else + { return "Error"; } } - - String searchUser(std::string message) - { - timeClient->update(); - time_t currentTime = (time_t) timeClient->getEpochTime(); -// Serial.print("Epoch: "); Serial.println(currentTime); - - if (message.empty()) { + + String searchUser(std::string message, bool extended = false) + { + gettimeofday(&_cbtime, NULL); + _now = time(nullptr); + time_t currentTime = _now; + + if (message.empty()) + { return "Error with search term!"; } std::string url = "https://api.twitter.com/1.1/users/search.json"; - + url += "?q="; url += misc::url_encode(message); - url += "&page=1&count=1"; + url += "&count=2&include_entities=false&page=1"; + if (extended) + url += "&tweet_mode=extended"; oauth::Request oauth_req = oauth::sign(url.c_str(), oauth::GET, keys(), currentTime); String res; @@ -1188,9 +1573,12 @@ class TwitterClient { char const *p = oauth_req.getdata.c_str(); char const *r = oauth_req.right.c_str(); opt.set_get_data(p, p + oauth_req.getdata.size(), r); - if(request(oauth_req.url, opt, &res)){ + if (request(oauth_req.url, opt, &res)) + { return res; - } else { + } + else + { return "Error"; } } diff --git a/examples/esp8266/TwitterSearch/TwitterSearch.ino b/examples/espdevices/TwitterSearch/TwitterSearch.ino similarity index 78% rename from examples/esp8266/TwitterSearch/TwitterSearch.ino rename to examples/espdevices/TwitterSearch/TwitterSearch.ino index b117197..1f1b926 100644 --- a/examples/esp8266/TwitterSearch/TwitterSearch.ino +++ b/examples/espdevices/TwitterSearch/TwitterSearch.ino @@ -1,9 +1,4 @@ -#include -#include -#include -#include -#include -#include // https://github.com/bblanchon/ArduinoJson +#include // https://github.com/bblanchon/ArduinoJson //#include "secret.h" // uncomment if using secret.h file with credentials //#define TWI_TIMEOUT 3000 // varies depending on network speed (msec), needs to be before TwitterWebAPI.h #include @@ -25,6 +20,8 @@ unsigned long twi_update_interval = 20; // (seconds) minimum 5s (180 API calls static char const accesstoken_sec[] = "bsekjH8YT3dCWDdsgsdHUgdBiosesDgv43rknU4YY56Tj"; #endif +#define LED_BUILTIN 2 + // Dont change anything below this line // /////////////////////////////////////////////// @@ -33,9 +30,8 @@ unsigned long api_lasttime = 0; bool twit_update = false; std::string search_msg = "No Message Yet!"; -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, ntp_server, timezone*3600, 60000); // NTP server pool, offset (in seconds), update interval (in milliseconds) -TwitterClient tcr(timeClient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); +WiFiClientSecure sclient; +TwitterClient tcr(sclient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); void setup(void){ //Begin Serial @@ -54,7 +50,7 @@ void setup(void){ Serial.println(WiFi.localIP()); delay(100); // Connect to NTP and force-update time - tcr.startNTP(); + tcr.startNTP(ntp_server, timezone); Serial.println("NTP Synced"); delay(100); // Setup internal LED @@ -63,15 +59,15 @@ void setup(void){ if (twi_update_interval < 5) api_mtbs = 5000; // Cant update faster than 5s. } -void extractJSON(String tmsg) { - const char* msg2 = const_cast (tmsg.c_str()); - DynamicJsonBuffer jsonBuffer; - JsonObject& response = jsonBuffer.parseObject(msg2); - - if (!response.success()) { +void extractJSON(String &tmsg) { + //const char* msg2 = const_cast (tmsg.c_str()); + DynamicJsonDocument response(5000); + auto error = deserializeJson(response, tmsg); + if(error) + { Serial.println("Failed to parse JSON!"); - Serial.println(msg2); -// jsonBuffer.clear(); + Serial.println(error.c_str()); + Serial.println(tmsg); return; } @@ -89,14 +85,15 @@ void extractJSON(String tmsg) { Serial.println("No useful data"); } - jsonBuffer.clear(); - delete [] msg2; + response.clear(); + //delete [] msg2; } void loop(void){ if (millis() > api_lasttime + api_mtbs) { digitalWrite(LED_BUILTIN, LOW); - extractJSON(tcr.searchTwitter(search_str)); + String reply = tcr.searchTwitter(search_str); + extractJSON(reply); Serial.print("Search: "); Serial.println(search_str.c_str()); Serial.print("MSG: "); diff --git a/examples/esp8266/TwitterSearch/secret.h b/examples/espdevices/TwitterSearch/secret.h similarity index 100% rename from examples/esp8266/TwitterSearch/secret.h rename to examples/espdevices/TwitterSearch/secret.h diff --git a/examples/esp8266/TwitterSearchUser/TwitterSearchUser.ino b/examples/espdevices/TwitterSearchUser/TwitterSearchUser.ino similarity index 77% rename from examples/esp8266/TwitterSearchUser/TwitterSearchUser.ino rename to examples/espdevices/TwitterSearchUser/TwitterSearchUser.ino index 50430ce..d575dd1 100644 --- a/examples/esp8266/TwitterSearchUser/TwitterSearchUser.ino +++ b/examples/espdevices/TwitterSearchUser/TwitterSearchUser.ino @@ -1,9 +1,4 @@ -#include -#include -#include -#include -#include -#include // https://github.com/bblanchon/ArduinoJson +#include // https://github.com/bblanchon/ArduinoJson //#include "secret.h" // uncomment if using secret.h file with credentials //#define TWI_TIMEOUT 3000 // varies depending on network speed (msec), needs to be before TwitterWebAPI.h #include @@ -25,6 +20,8 @@ unsigned long twi_update_interval = 20; // (seconds) minimum 5s (180 API calls static char const accesstoken_sec[] = "bsekjH8YT3dCWDdsgsdHUgdBiosesDgv43rknU4YY56Tj"; #endif +#define LED_BUILTIN 2 + // Dont change anything below this line // /////////////////////////////////////////////// @@ -33,9 +30,8 @@ unsigned long api_lasttime = 0; bool twit_update = false; std::string search_msg = "No Message Yet!"; -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, ntp_server, timezone*3600, 60000); // NTP server pool, offset (in seconds), update interval (in milliseconds) -TwitterClient tcr(timeClient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); +WiFiClientSecure sclient; +TwitterClient tcr(sclient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); void setup(void){ //Begin Serial @@ -54,7 +50,7 @@ void setup(void){ Serial.println(WiFi.localIP()); delay(100); // Connect to NTP and force-update time - tcr.startNTP(); + tcr.startNTP(ntp_server, timezone); Serial.println("NTP Synced"); delay(100); // Setup internal LED @@ -63,17 +59,17 @@ void setup(void){ if (twi_update_interval < 5) api_mtbs = 5000; // Cant update faster than 5s. } -void extractJSON(String tmsg) { - const char* msg2 = const_cast (tmsg.c_str()); -// DynamicJsonBuffer jsonBuffer; - const size_t bufferSize = 5*JSON_ARRAY_SIZE(0) + 4*JSON_ARRAY_SIZE(1) + 3*JSON_ARRAY_SIZE(2) + 3*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + 8*JSON_OBJECT_SIZE(3) + 3*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 2*JSON_OBJECT_SIZE(10) + JSON_OBJECT_SIZE(24) + JSON_OBJECT_SIZE(43) + 6060; - DynamicJsonBuffer jsonBuffer(bufferSize); - JsonObject& response = jsonBuffer.parseObject(msg2); - - if (!response.success()) { +void extractJSON(String &tmsg) { +// const char* msg2 = const_cast (tmsg.c_str()); + DynamicJsonDocument response(5000); +// const size_t bufferSize = 5*JSON_ARRAY_SIZE(0) + 4*JSON_ARRAY_SIZE(1) + 3*JSON_ARRAY_SIZE(2) + 3*JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + 8*JSON_OBJECT_SIZE(3) + 3*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 2*JSON_OBJECT_SIZE(10) + JSON_OBJECT_SIZE(24) + JSON_OBJECT_SIZE(43) + 6060; +// DynamicJsonDocument jsonBuffer(bufferSize); + auto error = deserializeJson(response, tmsg); + if(error) + { Serial.println("Failed to parse JSON!"); - Serial.println(msg2); -// jsonBuffer.clear(); + Serial.println(error.c_str()); + Serial.println(tmsg); return; } String namet = response["name"]; @@ -81,8 +77,8 @@ void extractJSON(String tmsg) { String text = namet + " has " + followers_count + " followers."; search_msg = std::string(text.c_str(), text.length()); - jsonBuffer.clear(); - delete [] msg2; + response.clear(); +// delete [] msg2; } void extractTweetText(String tmsg) { @@ -139,8 +135,9 @@ void extractTweetText(String tmsg) { void loop(void){ if (millis() > api_lasttime + api_mtbs) { digitalWrite(LED_BUILTIN, LOW); -// extractJSON(tcr.searchUser(search_str)); - extractTweetText(tcr.searchUser(search_str)); + String reply = tcr.searchUser(search_str); + //extractJSON(reply); + extractTweetText(reply); Serial.print("Search: "); Serial.println(search_str.c_str()); Serial.print("MSG: "); @@ -150,4 +147,4 @@ void loop(void){ delay(2); yield(); digitalWrite(LED_BUILTIN, HIGH); -} +} \ No newline at end of file diff --git a/examples/esp8266/TwitterSearchUser/secret.h b/examples/espdevices/TwitterSearchUser/secret.h similarity index 100% rename from examples/esp8266/TwitterSearchUser/secret.h rename to examples/espdevices/TwitterSearchUser/secret.h diff --git a/examples/esp8266/TwitterTweetSearch/TwitterTweetSearch.ino b/examples/espdevices/TwitterTweetSearch/TwitterTweetSearch.ino similarity index 91% rename from examples/esp8266/TwitterTweetSearch/TwitterTweetSearch.ino rename to examples/espdevices/TwitterTweetSearch/TwitterTweetSearch.ino index 4722673..9dd51ac 100644 --- a/examples/esp8266/TwitterTweetSearch/TwitterTweetSearch.ino +++ b/examples/espdevices/TwitterTweetSearch/TwitterTweetSearch.ino @@ -1,10 +1,14 @@ -#include -#include +#ifdef ESP32 +#include +#include +#include +#elif defined(ESP8266) #include #include -#include -#include -#include +#else +#error Platform not supported +#endif + #include // https://github.com/bblanchon/ArduinoJson //#include "secret.h" // uncomment if using secret.h file with credentials //#define TWI_TIMEOUT 3000 // varies depending on network speed (msec), needs to be before TwitterWebAPI.h @@ -29,6 +33,8 @@ unsigned long twi_update_interval = 20; // (seconds) minimum 5s (180 API calls static char const accesstoken_sec[] = "bsekjH8YT3dCWDdsgsdHUgdBiosesDgv43rknU4YY56Tj"; #endif +#define LED_BUILTIN 2 + // Dont change anything below this line // /////////////////////////////////////////////// @@ -37,21 +43,25 @@ unsigned long api_lasttime = 0; bool twit_update = false; std::string search_msg = "No Message Yet!"; -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, ntp_server, timezone*3600, 60000); // NTP server pool, offset (in seconds), update interval (in milliseconds) -TwitterClient tcr(timeClient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); +WiFiClientSecure sclient; +TwitterClient tcr(sclient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); +#ifdef ESP32 +WebServer server(80); +#elif defined(ESP8266) ESP8266WebServer server(80); +#endif -void extractJSON(String tmsg) { - const char* msg2 = const_cast (tmsg.c_str()); - DynamicJsonBuffer jsonBuffer; - JsonObject& response = jsonBuffer.parseObject(msg2); - - if (!response.success()) { + +void extractJSON(String &tmsg) { + //const char* msg2 = const_cast (tmsg.c_str()); + DynamicJsonDocument response(5000); + auto error = deserializeJson(response, tmsg); + if(error) + { Serial.println("Failed to parse JSON!"); - Serial.println(msg2); -// jsonBuffer.clear(); + Serial.println(error.c_str()); + Serial.println(tmsg); return; } @@ -69,8 +79,8 @@ void extractJSON(String tmsg) { Serial.println("No useful data"); } - jsonBuffer.clear(); - delete [] msg2; + response.clear(); + //delete [] msg2; } void handleRoot() { @@ -210,7 +220,7 @@ void setup(void){ Serial.println(WiFi.localIP()); delay(100); // Connect to NTP and force-update time - tcr.startNTP(); + tcr.startNTP(ntp_server, timezone); Serial.println("NTP Synced"); delay(100); // Setup internal LED @@ -246,7 +256,8 @@ void loop(void){ if (millis() > api_lasttime + api_mtbs) { digitalWrite(LED_BUILTIN, LOW); - extractJSON(tcr.searchTwitter(search_str)); + String reply = tcr.searchTwitter(search_str); + extractJSON(reply); Serial.print("Search: "); Serial.println(search_str.c_str()); Serial.print("MSG: "); diff --git a/examples/esp8266/TwitterTweetSearch/secret.h b/examples/espdevices/TwitterTweetSearch/secret.h similarity index 100% rename from examples/esp8266/TwitterTweetSearch/secret.h rename to examples/espdevices/TwitterTweetSearch/secret.h diff --git a/examples/esp8266/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino b/examples/espdevices/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino similarity index 87% rename from examples/esp8266/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino rename to examples/espdevices/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino index 29c5306..81fe0c1 100644 --- a/examples/esp8266/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino +++ b/examples/espdevices/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino @@ -1,15 +1,19 @@ #include // FS.h has to be first -#include +#ifdef ESP32 +#include +#include +#include +#include +#include +#elif defined(ESP8266) #include #include #include -#include -#include -#include +#else +#error Platform not supported +#endif #include -#include -#include -#include // https://github.com/bblanchon/ArduinoJson +#include // https://github.com/bblanchon/ArduinoJson //#include "secret.h" // uncomment if using secret.h file with credentials //#define TWI_TIMEOUT 3000 // varies depending on network speed (msec), needs to be before TwitterWebAPI.h #include @@ -64,6 +68,8 @@ unsigned long twi_update_interval = 20; // (seconds) minimum 5s (180 API calls #define ota_password "password" // OTA password #endif +#define LED_BUILTIN 2 + // Dont change anything below this line // /////////////////////////////////////////////// @@ -84,11 +90,16 @@ MD_Parola P = MD_Parola(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); char curmsg[512]; #endif -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP, ntp_server, timezone*3600, 60000); // NTP server pool, offset (in seconds), update interval (in milliseconds) -TwitterClient tcr(timeClient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); +WiFiClientSecure sclient; +TwitterClient tcr(sclient, consumer_key, consumer_sec, accesstoken, accesstoken_sec); + +#ifdef ESP32 +WebServer server(80); +const char* serverIndex = "
"; +#elif defined(ESP8266) ESP8266WebServer server(80); ESP8266HTTPUpdateServer httpUpdater; +#endif // Helper #define MODEBUTTON 0 @@ -150,23 +161,26 @@ bool writetoFS(bool saveConfig){ if (saveConfig) { //FS save DEBUG_PRINT("Mounting FS..."); +#ifdef ESP32 + if (SPIFFS.begin(true) and saveConfig) { +#else if (SPIFFS.begin() and saveConfig) { +#endif updateFS = true; DEBUG_PRINTLN("Mounted."); //save the custom parameters to FS DEBUG_PRINT("Saving config: "); // DynamicJsonBuffer jsonBuffer; - StaticJsonBuffer<200> jsonBuffer; - JsonObject& json = jsonBuffer.createObject(); + StaticJsonDocument<200> json; json["search"] = search_str.c_str(); // SPIFFS.remove("/config.json") ? DEBUG_PRINTLN("removed file") : DEBUG_PRINTLN("failed removing file"); File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) DEBUG_PRINTLN("failed to open config file for writing"); - - json.printTo(Serial); - json.printTo(configFile); + + serializeJson(json, Serial); + serializeJson(json, configFile); configFile.close(); updateFS = false; SPIFFS.end(); @@ -189,8 +203,13 @@ bool readfromFS() { //read configuration from FS json DEBUG_PRINT("Mounting FS..."); updateFS = true; +#ifdef ESP32 + if (resetsettings) { SPIFFS.begin(true); SPIFFS.remove("/config.json"); SPIFFS.format(); delay(1000);} + if (SPIFFS.begin(true)) { //format SPIFFS if needed +#else if (resetsettings) { SPIFFS.begin(); SPIFFS.remove("/config.json"); SPIFFS.format(); delay(1000);} if (SPIFFS.begin()) { +#endif DEBUG_PRINTLN("mounted file system"); if (SPIFFS.exists("/config.json")) { //file exists, reading and loading @@ -203,11 +222,10 @@ bool readfromFS() { std::unique_ptr buf(new char[size]); configFile.readBytes(buf.get(), size); -// DynamicJsonBuffer jsonBuffer; - StaticJsonBuffer jsonBuffer; - JsonObject& json = jsonBuffer.parseObject(buf.get()); - json.printTo(Serial); - if (json.success()) { +// DynamicJsonDocument jsonBuffer(5000); + StaticJsonDocument json; + auto error = deserializeJson(json, buf.get()); + if (!error) { DEBUG_PRINTLN("\nparsed json"); String tmpstr = json["search"]; search_str = std::string(tmpstr.c_str(), tmpstr.length()); @@ -231,18 +249,17 @@ bool readfromFS() { return false; } -void extractJSON(String tmsg) { - const char* msg2 = const_cast (tmsg.c_str()); - DynamicJsonBuffer jsonBuffer; - JsonObject& response = jsonBuffer.parseObject(msg2); - - if (!response.success()) { - DEBUG_PRINTLN("Failed to parse JSON!"); - DEBUG_PRINTLN(msg2); -// jsonBuffer.clear(); +void extractJSON(String &tmsg) { + // const char* msg2 = const_cast (tmsg.c_str()); + DynamicJsonDocument response(5000); + auto error = deserializeJson(response, tmsg); + if(error) + { + Serial.println("Failed to parse JSON!"); + Serial.println(error.c_str()); + Serial.println(tmsg); return; } - if (response.containsKey("statuses")) { String usert = response["statuses"][0]["user"]["screen_name"]; String text = response["statuses"][0]["text"]; @@ -257,8 +274,8 @@ void extractJSON(String tmsg) { DEBUG_PRINTLN("No useful data"); } - jsonBuffer.clear(); - delete [] msg2; + response.clear(); + // delete [] msg2; } void extractTweetText(String tmsg) { @@ -519,7 +536,7 @@ void setup(void){ delay(100); // Connect to NTP and force-update time - tcr.startNTP(); + tcr.startNTP(ntp_server, timezone); DEBUG_PRINTLN("NTP Synced"); delay(100); @@ -537,8 +554,38 @@ void setup(void){ } else { DEBUG_PRINTLN("Error setting up MDNS responder!"); } - +#ifdef ESP32 + server.on(ota_location, HTTP_POST, []() { + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + ESP.restart(); + }, []() { + if (!server.authenticate(ota_user, ota_password)) { + return server.requestAuthentication(); + } + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + DEBUG_PRINTF("Update: %s\n", upload.filename.c_str()); + if (!Update.begin()) { //start with max available size + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Update.printError(Serial); + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + DEBUG_PRINTF("Update Success: %u\nRebooting...\n", upload.totalSize); + } else { + Update.printError(Serial); + } + } else { + DEBUG_PRINTF("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status); + } + }); +#elif defined(ESP8266) httpUpdater.setup(&server,ota_location,ota_user,ota_password); +#endif server.on("/", handleRoot); server.on("/search", getSearchWord); server.on("/tweet", handleTweet); @@ -564,7 +611,7 @@ void loop(void){ wifiManager.addParameter(&custom_search_str); if (!wifiManager.startConfigPortal((const char *) String(String(HOSTNAME) + String("OnDemandAP")).c_str(), AutoAP_password)) { DEBUG_PRINTLN("failed to connect and hit timeout"); - delay(3000); ESP.reset(); delay(5000); + delay(3000); ESP.restart(); delay(5000); } search_str = std::string(custom_search_str.getValue()); //read updated parameters if(writetoFS(shouldSaveConfig)) DEBUG_PRINTLN("Done writing");; //FS save @@ -592,4 +639,4 @@ void loop(void){ yield(); delay(2); //do something or else esp8266 is not happy, can remove this line if doing something else digitalWrite(LED_BUILTIN, HIGH); -} +} \ No newline at end of file diff --git a/examples/esp8266/TwitterTweetSearchFSWiFiMgr/secret.h b/examples/espdevices/TwitterTweetSearchFSWiFiMgr/secret.h similarity index 100% rename from examples/esp8266/TwitterTweetSearchFSWiFiMgr/secret.h rename to examples/espdevices/TwitterTweetSearchFSWiFiMgr/secret.h diff --git a/keywords.txt b/keywords.txt index 364264e..db2832b 100644 --- a/keywords.txt +++ b/keywords.txt @@ -14,12 +14,13 @@ TwitterWebAPI KEYWORD1 tweet KEYWORD2 searchTwitter KEYWORD2 searchUser KEYWORD2 +consumer_key KEYWORD2 +consumer_sec KEYWORD2 +accesstoken KEYWORD2 +accesstoken_sec KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### -consumer_key LITERAL1 -consumer_sec LITERAL1 -accesstoken LITERAL1 -accesstoken_sec LITERAL1 + diff --git a/library.json b/library.json index e414c06..1cf221c 100644 --- a/library.json +++ b/library.json @@ -8,6 +8,6 @@ "url": "https://github.com/debsahu/TwitterWebAPI.git" }, "frameworks": "arduino", - "platforms": "espressif8266", - "version": "0.1" + "platforms": ["espressif8266", "espressif32"], + "version": "1.0" } diff --git a/library.properties b/library.properties index f1dcb60..a3dfa37 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=TwitterWebAPI -version=0.1 +version=1.0 author=debsahu maintainer=debsahu sentence=Twitter Web API