From 94d21da617362b538c494ff738824bd339a9e05d Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 15 Aug 2020 20:08:33 -0700 Subject: [PATCH] =?UTF-8?q?Twitter=20Hash=20Search=20Application=20with=20?= =?UTF-8?q?#=EF=B8=8F=E2=83=A3NodeMCU(ESP8266)=20#=EF=B8=8F=E2=83=A3ILI934?= =?UTF-8?q?1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ESP8266_Twitter_Hash_Search.ino | 184 ++++++++++++++++++ ESP8266_Twitter_Hash_Search/List_SPIFFS.h | 88 +++++++++ ESP8266_Twitter_Hash_Search/Web_Fetch.h | 78 ++++++++ .../data/placeholder.jpg | Bin 0 -> 12744 bytes 4 files changed, 350 insertions(+) create mode 100644 ESP8266_Twitter_Hash_Search/ESP8266_Twitter_Hash_Search.ino create mode 100644 ESP8266_Twitter_Hash_Search/List_SPIFFS.h create mode 100644 ESP8266_Twitter_Hash_Search/Web_Fetch.h create mode 100644 ESP8266_Twitter_Hash_Search/data/placeholder.jpg diff --git a/ESP8266_Twitter_Hash_Search/ESP8266_Twitter_Hash_Search.ino b/ESP8266_Twitter_Hash_Search/ESP8266_Twitter_Hash_Search.ino new file mode 100644 index 0000000..7734453 --- /dev/null +++ b/ESP8266_Twitter_Hash_Search/ESP8266_Twitter_Hash_Search.ino @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include +// Include SPIFFS +#define FS_NO_GLOBALS +#include +#include "List_SPIFFS.h" +#include "Web_Fetch.h" +#include "SPI.h" +#include "TFT_eSPI.h" +TFT_eSPI tft = TFT_eSPI(); + +#ifndef WIFICONFIG +const char* ssid = ""; +const char* password = ""; +#endif + +std::string search_str = "#WeWantToPlay"; // Default search word for twitter +const char *ntp_server = "pool.ntp.org"; // time1.google.com, time.nist.gov, pool.ntp.org +int timezone = -7; // Pacific Daylight Time -07:00 HRS +unsigned long twi_update_interval = 10; // (seconds) minimum 5s (180 API calls/15 min). Any value less than 5 is ignored! + +#ifndef TWITTERINFO // Obtain these by creating an app @ https://apps.twitter.com/ + static char const consumer_key[] = ""; + static char const consumer_sec[] = ""; + static char const accesstoken[] = ""; + static char const accesstoken_sec[] = ""; +#endif + +const char* userProfileImage = "/profile_user.jpg"; +const char* placeholderImage = "/placeholder.jpg"; + +unsigned long api_mtbs = twi_update_interval * 1000; //mean time between api requests +unsigned long api_lasttime = 0; +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); + +void initSPIFFS(){ + // Initialise SPIFFS + if (!SPIFFS.begin()) { + Serial.println("SPIFFS initialisation failed!"); + while (1) yield(); // Stay here twiddling thumbs waiting + } + Serial.println("\r\nInitialisation done."); + + //SPIFFS.format(); //Only for the first time! + listSPIFFS(); +} + +bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) +{ + if ( y >= tft.height() ) return 0; + tft.pushImage(x, y, w, h, bitmap); + return 1; +} + +void initTFT(){ + tft.init(); + tft.fillScreen(TFT_BLACK); + tft.setRotation(3); + tft.setTextColor(TFT_WHITE); + tft.setFreeFont(&FreeSans9pt7b); + tft.setCursor(0, 30); + tft.println("Twitter Hashtag Search"); + + TJpgDec.setJpgScale(1); + TJpgDec.setSwapBytes(true); + TJpgDec.setCallback(tft_output); +} + +void printToTFT(String msg){ + tft.println(msg); +} + +void showTweet(){ + tft.fillScreen(TFT_BLACK); + tft.setTextColor(TFT_WHITE); + tft.setFreeFont(&FreeSansBold12pt7b); + tft.setCursor(120, 40); + tft.println(search_str.c_str()); + + tft.setFreeFont(&FreeSans9pt7b); + tft.setCursor(0, 80); + tft.println(search_msg.c_str()); +} + +void setup(void){ + Serial.begin(115200); + + initSPIFFS(); + initTFT(); + + // WiFi Connection + WiFi.begin(ssid, password); + Serial.print("\nConnecting to "); + Serial.print(ssid); + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("Connected. yay!"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + printToTFT("WIFI Connected!"); + + delay(100); + // Connect to NTP and force-update time + tcr.startNTP(); + Serial.println("NTP Synced"); + + printToTFT("NTP Synced!"); + + delay(100); + + printToTFT("Loading..."); +} + +void extractJSON(String tmsg) { + //Serial.println(tmsg); + size_t len = tmsg.length(); + Serial.printf("msg length: %d\n", len); + if(len < 1000) return; + + DynamicJsonDocument doc(len*2); + DeserializationError error = deserializeJson(doc, tmsg, DeserializationOption::NestingLimit(20)); + if(error){ + Serial.print(F("DeserializeJson() failed: ")); + Serial.println(error.c_str()); + return; + } + + if(doc.containsKey("statuses")){ + String userT = doc["statuses"][0]["user"]["screen_name"]; + String text = doc["statuses"][0]["text"]; + if(text != ""){ + search_msg = "@" + userT + " says " + text; + } + + showTweet(); + TJpgDec.drawFsJpg(10, 10, placeholderImage); + + String profile_image_url = doc["statuses"][0]["user"]["profile_image_url"]; + Serial.println(profile_image_url); + + if(profile_image_url.indexOf("_normal") > 0){ + + bool loaded_ok = getFile(profile_image_url, userProfileImage); + listSPIFFS(); + + if(loaded_ok){ + uint16_t w = 0, h = 0; + TJpgDec.getFsJpgSize(&w, &h, userProfileImage); + if(w !=0 && h != 0){ + TJpgDec.drawFsJpg(10, 10, userProfileImage); + } + } + } + + }else if(doc.containsKey("errors")){ + String err = doc["errors"][0]; + search_msg = err; + }else{ + Serial.println("No Useful Data"); + } +} + +void loop(void){ + if (millis() > api_lasttime + api_mtbs) { + extractJSON(tcr.searchTwitter(search_str)); + api_lasttime = millis(); + } + delay(2); + yield(); +} diff --git a/ESP8266_Twitter_Hash_Search/List_SPIFFS.h b/ESP8266_Twitter_Hash_Search/List_SPIFFS.h new file mode 100644 index 0000000..03914ec --- /dev/null +++ b/ESP8266_Twitter_Hash_Search/List_SPIFFS.h @@ -0,0 +1,88 @@ +//https://github.com/Bodmer/TJpg_Decoder +/*************************************************************************************** +** Function name: listSPIFFS +** Description: Listing SPIFFS files +***************************************************************************************/ +#ifdef ESP8266 +void listSPIFFS(void) { + Serial.println(F("\r\nListing SPIFFS files:")); + + fs::Dir dir = SPIFFS.openDir("/"); // Root directory + + static const char line[] PROGMEM = "================================================="; + Serial.println(FPSTR(line)); + Serial.println(F(" File name Size")); + Serial.println(FPSTR(line)); + + while (dir.next()) { + String fileName = dir.fileName(); + Serial.print(fileName); + int spaces = 33 - fileName.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + + fs::File f = dir.openFile("r"); + String fileSize = (String) f.size(); + spaces = 10 - fileSize.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + Serial.println(fileSize + " bytes"); + } + + Serial.println(FPSTR(line)); + Serial.println(); + delay(1000); +} + +//==================================================================================== + +#elif defined ESP32 + +void listSPIFFS(void) { + Serial.println(F("\r\nListing SPIFFS files:")); + static const char line[] PROGMEM = "================================================="; + + Serial.println(FPSTR(line)); + Serial.println(F(" File name Size")); + Serial.println(FPSTR(line)); + + fs::File root = SPIFFS.open("/"); + if (!root) { + Serial.println(F("Failed to open directory")); + return; + } + if (!root.isDirectory()) { + Serial.println(F("Not a directory")); + return; + } + + fs::File file = root.openNextFile(); + while (file) { + + if (file.isDirectory()) { + Serial.print("DIR : "); + String fileName = file.name(); + Serial.print(fileName); + } else { + String fileName = file.name(); + Serial.print(" " + fileName); + // File path can be 31 characters maximum in SPIFFS + int spaces = 33 - fileName.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + String fileSize = (String) file.size(); + spaces = 10 - fileSize.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + Serial.println(fileSize + " bytes"); + } + + file = root.openNextFile(); + } + + Serial.println(FPSTR(line)); + Serial.println(); + delay(1000); +} + +#endif diff --git a/ESP8266_Twitter_Hash_Search/Web_Fetch.h b/ESP8266_Twitter_Hash_Search/Web_Fetch.h new file mode 100644 index 0000000..ce795b7 --- /dev/null +++ b/ESP8266_Twitter_Hash_Search/Web_Fetch.h @@ -0,0 +1,78 @@ +//https://github.com/Bodmer/TJpg_Decoder +// Fetch a file from the URL given and save it in SPIFFS +// Return 1 if a web fetch was needed or 0 if file already exists +bool getFile(String url, String filename) { + + // If it exists then no need to fetch it +// if (SPIFFS.exists(filename) == true) { +// Serial.println("Found " + filename); +// return 0; +// } + + Serial.println("Downloading " + filename + " from " + url); + + // Check WiFi connection + if ((WiFi.status() == WL_CONNECTED)) { + HTTPClient http; + + Serial.print("[HTTP] begin...\n"); + + // Configure server and url + http.begin(url); + + Serial.print("[HTTP] GET...\n"); + // Start connection and send HTTP header + int httpCode = http.GET(); + if (httpCode > 0) { + fs::File f = SPIFFS.open(filename, "w+"); + if (!f) { + Serial.println("file open failed"); + return 0; + } + // HTTP header has been send and Server response header has been handled + Serial.printf("[HTTP] GET... code: %d\n", httpCode); + + // File found at server + if (httpCode == HTTP_CODE_OK) { + + // Get length of document (is -1 when Server sends no Content-Length header) + int total = http.getSize(); + int len = total; + + // Create buffer for read + uint8_t buff[128] = { 0 }; + + // Get tcp stream + WiFiClient * stream = http.getStreamPtr(); + + // Read all data from server + while (http.connected() && (len > 0 || len == -1)) { + // Get available data size + size_t size = stream->available(); + + if (size) { + // Read up to 128 bytes + int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + + // Write it to file + f.write(buff, c); + + // Calculate remaining bytes + if (len > 0) { + len -= c; + } + } + yield(); + } + Serial.println(); + Serial.print("[HTTP] connection closed or file end.\n"); + } + f.close(); + } + else { + Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + http.end(); + } + return 1; // File was fetched from web +} diff --git a/ESP8266_Twitter_Hash_Search/data/placeholder.jpg b/ESP8266_Twitter_Hash_Search/data/placeholder.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ba9e146fd74f83bc40df6094ec5d46a0993a11fc GIT binary patch literal 12744 zcmeG?XIN87*K<<|y$c8udeJ}<2!tp~F9L#8yN2WvA|Z(>6kWlB-L>mtMbQPjyDm2D zSP*qpEZBEJ77Lcef+#5Y=HAfU^?l!c-tYN-eNLXtoH^&rnKNh3)H|9xng)_rc%C=| zfVeo|2>{RmRKyezA%wsmKx_aRhXF`O`uD(-5%(@00_0%<7y}3RS0HR0Hiz)ad1657 zD!Uz~H-YY_834E$cc;NZSvpEj$dsvM%1oJ@&SrVB>F#lSfmn)RJOG=?X7ZR^9*a$9 zv3b7!JdQ7vL)?t?>bIHDQwMW1u@{DXSfC)pE?!FCwjhqZZ9zQywuOLn%RtzK9@&I< zT5%u>rqC+dRC7o366oWtm>3!d5X~!Ki06QC2F9nk4~l@kj*bpZN1sO1H__A8GqEtz z*Eh1TGBdL{|6-N#okt2UJCvLdOa;k7`? zu`Jy`+cj)uvM{`AtIDt6^^`~Gn_r5~+)y{Zjc{AFZBE7Nvp4^E_v3+cx0+g0Gjisx zDLZ(+?(uv7$TU&zydTOBRo{NnW&#KVs4WTCiAo`Ja1|U_rX;Ar6h|{Md**U;OoP*j zwJko*=?#imD?*bj1WMo6E)+}wsw=1JI#gn7KVf)^p9+1{qr$(|z^zvW%{`z`#OpKx z!@#@b6`>ankBr&9z$s#>X4V}}lwN0|TQ?P36h=I0&)9y8N9H`NNfEtejGxVlx>zua0-q2*+ zAG7Z~o>iqc*s^8w6X$YG?K^bo+GVne=k0!0lQiIDp*U+u$M}|E%O_n&hl~{_4gmfCt9U3<}Mr)11mKJnL0g4R*z-z5UG&<4HqK$ zJ70)Gn4BV2MK~o&u)ggHArc||YxqQ^M1|uM@+3itJ?y$Us6ZuCgz;5;tOdA|38DmL zw^v{=#tc(TZ4irQXvO@c+GM!0e689ffkcZ>PzZuXV-oOSEKtbgX_+YOTJ%bVsbtv_nN-wOM8BJYsn7>+(2Y+Ni!%EX^tuU9w;q0MtPo#nb?91WknR*8 zN3J-{u&Yv-44VP8{$_yVu(fso&T0K+p7<8e)9@HZ2wn=V!&uPm8YB>nYtV{gqkwlt z2Bb(}3|CGVkS@%Ywd;B?Y#cuZ#@##|9D{j&7Y3L40p0HyCe<3AbhW&?E@PuOT^|W zZOgGNfXlA-Zv|T~0em2UvrHzOaU?(pAq8N-8AsU#`C^MS$OGE6Yu3S~^hRAn6-RW- z?xjMgFGDw>s{vS6s+M4LC1wUnx=bw~(T1Pf<$50#unQiN%H~@+l$27I+j7Kutx1z&{Ji|w8ab-K}CFw-(+imAa8v8dZpM%u8~r7_Ub-k$CXp%n7}rEAKP0k;0E>VTGAU+jno1^zi@Xx; zJNxNM;57L+tbRH^t^N(K2fH})uk85fbjK=!t2lJ$VJ$eU9f{^RjJYro=fgMvv3n3; zu8IAZ0ni^NaA$7?ck17E^51sy|Ic>v_}FlOLjZs@_=l??+**TRxG>2;I$VfrIPc!y`@SoK6VG-C6Hx% z=kevF`tj%l1YW}G|}%@jvXRiH^zMj>Cr1hvf0{vgO&RicinWmPnPnyg)}>I1j=Y+S`$iv#7EH9r05-eN3&{JhJeXr)BOXySbn~~e#}91?C!yv$?;~f87w~@)0fBcr*{QMSerta!AlH_=&B3m z0v)>=m7AOEmFwdrQ;57-0RaKtOtv?h&43&XWxiC!&tpiHPTdm1P^Ca2#!mxuOd{Or zs#SrGP*ZIavgJKuziO*)MxmgGPY#zIymFzy8}2fDh{NOfF_`{5CR58ACliV@^1sT8 zT`zsX3Y|s>Papr0St#IT$Q0Rp*ko}wUxa#3mZKs^dXFV}_|gb#hU)qB#YcyR!Xspc zSON)3c>JPAhll#H0{r|KY%dnBZ(JNNTB=m>r2;fMEYK0!$4e|0^3u6n{|q+j!{GCM z*$iKw3>JgS_7^bxx$JabG{axO&PeYrA0`v1F)zT%YX^i-CV(<})#Lm5usAFMlfg;% z4PbBuzAQ#Mo5N)=GkpBHK1^}H44EC%+>??b!i;cw|T3w*P{Hw%2Tz&8tgv%vo& z3-q24Q7PP76JCr5V|c+lYymrIXAb0w%s#Ylk1cD_Og z57pUnzBC^IZJV)D!1Qki92~8U{w=W&-@gN~zSYv-j>P*6vAo=i>n+)fla<1|eE7YC zxU3hKz7~K(a{#cq*Nb!B3~xq?062KHPkSsef9Z4N7o!3%%%DBtKQi=i{%fF5eO_37 zJ%>YjnDz>nj(N2JUdgHzbS1nsLFtUYI`My9u#Z}O^ca+gW}pfb9!Tk_(97TlGT7Zx zp%^=>(Z$j)j<9_s{FlZ0NWeg?UqirbTPHA|=mm_gngim?%YbC22Z-Y~K?>5-Z?Uvg zc!dRTx9p#2{T{-Qj>lg_grzV`P>S)-0w6RonJ!Q(a$p&Ow@<1|=98DfY7LjwwbIEJSW#j|obL3m(Ch~g_f)``#=qa)Rs zrL#t7r_KqT>pD+#I%vi;7aETiPZQGeX-jF_XqB|9v?f}Iu8FRjZlG?m?quDWy2ZMC zb1yXn5W5l@ZOz#VFWlg3(l?HAcS} zT{U`POfz;h4mIW*7Z|TMK4N^^xYfkWgk=(Ml5MisWT#1u$x~CRsjF$YsnE2@bgStZ z(?&C*nUh(lnZRte**3FtW=-Z4b2sy7^U3Cm%=egIF>kRjweYn_waBwrZ*knB-jZnP zY8h=Qv0Q4o-?GlKy`Npb;C^Vo1^xE+yWX$Os=rl`6>7E6YM<3Dt9EO;b+~nw^>XV& z)(>q+Hl8*kZE|fk*_^X^(cirP;QoUC3;XZyf6ta+>uH;4JI!{h?Pc3my8(8Qc5=J5 zc4zEf46qy!G+^?8RRc~8cxG>AKh$1qzsmlkeKXyH9!!_ei|J?SEe^I0Q4VT{Ee=;4 zIvm{`QygbG?sI(Tr0c|W%5YlcROQs-Z0{WB{GIbI=X)+R7p_aD%MUKqE^V%Eu4%6G zTo1WEb+dAdcAM(9%k6=?f%`D`Dek52H$5mGT#qb|jULxL37#C!OwaY6R|gUX`VABh z+&Hjy5P49*AnBmeLAM$Dj9^9%V;7^r%hD^>YnIm`uUFpA-ebI%dslmRGC52Ma~tzM z%ak>oHH%frdc*c)3)t(~H+>9zB76#c4*R_J9q5btZt}guG2_H>=5eYxpZ)y(6n=aC zp7}fbPx9a3f17L0P2eu(UI-uugai}@91Hl!^W&*_`+0v3W(>|6ymRo=A#Ov^A=`#D z208}v1Gfa$52X*CG<5UO`e6>k_`^zvJqmIP5(bq8Jqh**o*cY8_*DoqL=kczq%D*e zIz9AM7%?m|Y+=}yaMSRV@O9z8ML0%?B6dZzL~?90~4o|Jt=YAVf=o|O%d<;iO01Lbq&^;39LR!?bB z#3;&?h;qF0u*w{6rY@`9)pOJhIYV>S=X}Ub&HW|MBu|=mIo~sXLH?7e5mSGhMw%v= zR`s33cQe0hm>xWRO94`lUQku&R9IBlG$V4x_L;gfvu6G}i#cn>tar21W>*&370oDW zoD(@`*IdK7in+Jv4W3srpEO@Q|H=ZN1#1^{E)*`jxQMxE&7x0>1&c3!&-%Xjd(D!J zC0CaEE#0(?vP`p>xpWs{mwMN;-Q#v&+Y`3u z@LtB=E&D9@E&2)kr2eV-=Se@;?T^`C^$Yixy%jDM8xEKqSa=XQn18V4Q0Aff!=n$^ z9vOb5s&a7UfumkW%a1u6TYuc*_|g-)CyGvJPEI}9dP;uk#c9#$#;S=`_s@(vbMx%T zvscc=oV##7;{2KF;OdhX0xuk^;nf_u$h~;zlHa9+mpPXY{L1k55fSj}0ELd1C)$$5ZyxW6vU< zU27iO-1J=jyyL~(mnJVaymEh4@n_JVms>`)G`&{5*1TE#*7|MvJKuLztqHBaz0ZE% z(YEk|^@r^rxgV?B)7qbOQKle8c`R%ZiTNp0fh+b z?;%1GkxI}(Xh2ub7!VL5eAh|DG$K%FY6QOcG$ET(9H?fFK!;@>%yzP%jSR8mJNxuY zoHElYeYs2MmY-W(eHFUar)xQG4M~2pUau$(OIC(Q_(uxdQ&Q7x_E%kh^hP*awXz7^ z`pcOcjUL>Lw`$J-UR3mOxLFfriYMpf=H*YFGk4zn1q)ZLUh_lo+I8E0EGyr>V`s&I zgNF_usXTk`eD#Hzi#Ko8-M(}8-XBempFDlm{I2zV+lP>=I*nPp1k2>CQn_sZfE7id(G`%J{)}rRw`@SOKRT#8K-_; z(p-4Iq~Q6(uzeRAUo#o0W8H!mmaUxsq~>-~LL}qF3A288b^*uC&6h7Rb)Qwbow%|lPe9zGo@epH#q8aSt-n9IY)4hoj*Sy+?)7_Z z8ms}F6rBpr&HaW0tV5l4IR0Kzu$k**H*FW`PRDPIwhGp?RpAycy`4uk8u-4hNW77ff82)=8=H2PIAq4M%_Y-pGr6s=<_|5*RA-2sl0Q$s`$59- zU%hZr?jFB1yLgKlo;;wsOm^)29u1HryDzuAltbuzNS=2VHo_)wYaMs;2m8XDoc`X? zO%q-9gh@?`fsV7~M|R&!@~mwv=_nwxe>!~l_~-ULvECClw?4fWUVQcs)~b|Zn`7z> zlO2uo`ujONT4`s;@y#;#t#~w)J|^#`_kvlKNb`XE4b@A>cJN~B>Yh1I?!0!g!<0*! z9`bw1w2zyP-`3oCNJ|?xljGSeuPnZs`uJXPr$s^er@*5(#;0n4JjX7h^P$BY5&xh( zWp#3YneE=+C6?>b%1byy(rf%;+y+THLyVT#xv%MWw7J68r>>IH>}u)%WcF#>!g;p8 zKdh{q8lRF3bS!m;o?QCg{zggL$%5yRoKzgaRU_|xo{ z`@Htu*4%t>#$#Y)@M@3kyhr;l7mZ_AHtwA7)NWi*()jY#y5?=>zrMe=(zd90t84Rv zjr0|HpTpk1j7)j9Zf|t{{^-K1Kc2k5DX?RYYVDpC6H~S>`