
It automatically connects to Wi-Fi and obtains real-time updates of the CNN Fear Index. You can reprogram it with Arduino just like you would a regular ESP32C3.
Order link
Example code is as follows:
/**************************************************************************This is an example for our Monochrome OLEDs based on SSD1306 drivers
Pick one up today in the adafruit shop!------> http://www.adafruit.com/category/63_98
This example is for a 128x32 pixel display using SPI to communicate4 or 5 pins are required to interface.
Adafruit invests time and resources providing this opensource code, please support Adafruit and open-sourcehardware by purchasing products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries,with contributions from the open source community.BSD license, check license.txt for more informationAll text above, and the splash screen below must beincluded in any redistribution.**************************************************************************/
#include <WiFi.h>#include <ArduinoJson.h>#include <HTTPClient.h>#include <time.h>
#include <SPI.h>#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h>
// ==================== WiFi Configuration ====================const char* ssid = "TELUSxxxx";const char* password = "xxxxxxx";
// ==================== OLED Configuration ====================#define SCREEN_WIDTH 128#define SCREEN_HEIGHT 64#define OLED_RESET -1#define SCREEN_ADDRESS 0x3CAdafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ==================== NTP Time Server Configuration ====================const char* ntpServer = "pool.ntp.org";const long gmtOffset_sec = -28800; // Vancouver timezone UTC-8 (Pacific Time)const int daylightOffset_sec = 3600; // Daylight saving time +1 hour
// ==================== Data Structure ====================struct FearGreedData {float value;String classification;String timestamp;bool isValid;};
// ==================== Global Variables ====================FearGreedData lastData;unsigned long lastFetchTime = 0;const unsigned long fetchInterval = 3600000; // Update every hour
// ==================== Function Declarations ====================void connectWiFi();void initTime();FearGreedData fetchCnnFearGreedIndex();void displayFearGreedIndex(const FearGreedData& data, int hour, int minute);String getClassificationSymbol(float value);String getClassificationText(float value);void drawProgressBar(int x, int y, int width, int height, float value, float minVal, float maxVal);
// ==================== Initialization ====================void setup() {Serial.begin(115200);delay(100);// Initialize I2C (SDA=3, SCL=4)Wire.begin(3, 4);// Initialize OLEDif(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {Serial.println(F("SSD1306 initialization failed"));for(;;);}
// Show initial display buffer contents on the screen --// the library initializes this with an Adafruit splash screen.display.display();delay(2000); // Pause for 2 seconds
display.clearDisplay();display.setTextSize(1);display.setTextColor(SSD1306_WHITE);display.setCursor(0, 0);display.println(F("Initializing..."));display.println(F("Connecting WiFi..."));display.display();// Connect WiFiconnectWiFi();// Initialize timedisplay.println(F("Syncing time..."));display.display();initTime();display.println(F("Ready!"));display.display();delay(2000);// Initialize datalastData.isValid = false;lastFetchTime = 0;}
// ==================== Main Loop ====================void loop() {// Get current timestruct tm timeinfo;int currentHour = 0;int currentMinute = 0;if(getLocalTime(&timeinfo)) {currentHour = timeinfo.tm_hour;currentMinute = timeinfo.tm_min;}// Periodically fetch fear & greed index dataunsigned long currentTime = millis();if(currentTime - lastFetchTime >= fetchInterval || !lastData.isValid) {lastData = fetchCnnFearGreedIndex();lastFetchTime = currentTime;}// Display datadisplayFearGreedIndex(lastData, currentHour, currentMinute);// Refresh display every 10 seconds (update time)delay(10000);}
// ==================== WiFi Connection ====================void connectWiFi() {const unsigned long timeout = 15000;unsigned long start = millis();
WiFi.begin(ssid, password);Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {Serial.print(".");display.print(".");display.display();
if (millis() - start > timeout) {Serial.println("\nConnection timeout, retrying...");display.println("\nRetrying...");display.display();WiFi.disconnect(true);delay(1000);WiFi.begin(ssid, password);start = millis();}delay(500);}
Serial.println("\nWiFi Connected");display.println("\nWiFi OK!");display.display();delay(1000);}
// ==================== Time Synchronization ====================void initTime() {configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);struct tm timeinfo;int retry = 0;while(!getLocalTime(&timeinfo) && retry < 10) {Serial.println("Failed to get time, retrying...");delay(1000);retry++;}if(retry < 10) {Serial.println("Time synchronized successfully");} else {Serial.println("Time synchronization failed");}}
// ==================== Fetch CNN Fear & Greed Index ====================FearGreedData fetchCnnFearGreedIndex() {FearGreedData data;data.isValid = false;if(WiFi.status() != WL_CONNECTED) {Serial.println("WiFi not connected");return data;}// ---------- Get yesterday's date ----------time_t now = time(nullptr);struct tm timeinfo;localtime_r(&now, &timeinfo);timeinfo.tm_mday -= 1; // Yesterdaymktime(&timeinfo);char dateStr[11];strftime(dateStr, sizeof(dateStr), "%Y-%m-%d", &timeinfo);String url = "https://production.dataviz.cnn.io/index/fearandgreed/graphdata/" + String(dateStr);Serial.print("Request URL: ");Serial.println(url);HTTPClient http;http.begin(url);// Critical: Complete browser request headershttp.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36");http.addHeader("Accept", "application/json, text/plain, */*");http.addHeader("Accept-Language", "en-US,en;q=0.9");http.addHeader("Referer", "https://www.cnn.com/markets/fear-and-greed"); // Simulate coming from official sitehttp.addHeader("Origin", "https://www.cnn.com");http.addHeader("Connection", "keep-alive");http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);http.setTimeout(10000);Serial.println("Requesting CNN Fear & Greed Index...");int httpCode = http.GET();if(httpCode == HTTP_CODE_OK) {String payload = http.getString();Serial.println("CNN API response successful, parsing...");// Memory allocation: conservative 16KBDynamicJsonDocument doc(16384);DeserializationError error = deserializeJson(doc, payload);if(!error) {// Parse fear_and_greed nodeif(doc.containsKey("fear_and_greed")) {JsonObject fg = doc["fear_and_greed"];data.value = fg["score"].as<float>();data.classification = fg["rating"].as<String>();if(fg.containsKey("timestamp")) {data.timestamp = fg["timestamp"].as<String>();}data.isValid = true;Serial.print("✓ CNN Index Success! Score: ");Serial.print(data.value);Serial.print(" | Rating: ");Serial.println(data.classification);} else {Serial.println("'fear_and_greed' node not found in JSON");}} else {Serial.print("JSON parsing failed: ");Serial.println(error.c_str());}} else {Serial.print("CNN API request failed, HTTP code: ");Serial.println(httpCode);if(httpCode == 418) {Serial.println("Server returned 418 I'm a teapot - IP may be temporarily blocked");}}http.end();return data;}
// ==================== Get Status Symbol (ASCII alternative to emoji) ====================String getClassificationSymbol(float value) {if(value <= 25) return "!!!"; // Extreme Fearif(value <= 45) return ":-("; // Fearif(value <= 55) return ":-|"; // Neutralif(value <= 75) return ":-)"; // Greedreturn ":-D"; // Extreme Greed}
// ==================== Get Status Text ====================String getClassificationText(float value) {if(value <= 25) return "Extreme Fear";if(value <= 45) return "Fear";if(value <= 55) return "Neutral";if(value <= 75) return "Greed";return "Extreme Greed";}
// ==================== Draw Progress Bar ====================void drawProgressBar(int x, int y, int width, int height, float value, float minVal, float maxVal) {// Calculate fill widthint fillWidth = map((int)value, (int)minVal, (int)maxVal, 0, width);fillWidth = constrain(fillWidth, 0, width);// Draw background framedisplay.drawRect(x, y, width, height, SSD1306_WHITE);// Choose fill style based on valueif(value <= 25) {// Extreme Fear - sparse dotsfor(int i = 0; i < fillWidth; i += 2) {for(int j = 0; j < height; j += 2) {display.drawPixel(x + i, y + j, SSD1306_WHITE);}}} else if(value <= 45) {// Fear - dense dotsfor(int i = 0; i < fillWidth; i++) {for(int j = 0; j < height; j += 2) {display.drawPixel(x + i, y + j, SSD1306_WHITE);}}} else {// Neutral/Greed/Extreme Greed - solid filldisplay.fillRect(x, y, fillWidth, height, SSD1306_WHITE);}}
// ==================== Display Fear & Greed Index ====================void displayFearGreedIndex(const FearGreedData& data, int hour, int minute) {display.clearDisplay();// ========== Display time (large font) ==========display.setTextSize(2);display.setCursor(0, 0);char timeStr[6];sprintf(timeStr, "%02d:%02d", hour, minute);display.println(timeStr);// ========== Separator line ==========display.setTextSize(1);display.drawLine(0, 16, SCREEN_WIDTH, 16, SSD1306_WHITE);if(data.isValid) {// ========== Display index value ==========display.setTextSize(2);display.setCursor(0, 20);display.print((int)data.value);display.setTextSize(1);display.print(" pts");// ========== Display status symbol ==========display.setCursor(SCREEN_WIDTH - 20, 22);display.setTextSize(1);display.print(getClassificationSymbol(data.value));// ========== Display classification text ==========display.setTextSize(1);display.setCursor(0, 38);display.print(getClassificationText(data.value));// ========== Display data source ==========display.setCursor(SCREEN_WIDTH - 18, 38);display.setTextSize(0);display.print("CNN");// ========== Draw progress bar ==========drawProgressBar(0, 48, SCREEN_WIDTH, 8, data.value, 0, 100);// ========== Progress bar labels ==========display.setTextSize(0);display.setCursor(0, 57);display.print("0");display.setCursor(SCREEN_WIDTH - 18, 57);display.print("100");display.setCursor(SCREEN_WIDTH/2 - 6, 57);display.print("50");} else {// ========== Display error message ==========display.setTextSize(1);display.setCursor(0, 25);display.println("Unable to get data");display.setCursor(0, 35);display.println("Check network");}display.display();}