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 0000000..ba9e146 Binary files /dev/null and b/ESP8266_Twitter_Hash_Search/data/placeholder.jpg differ