mdraber
Published © LGPL

Building Arduino Crypto Price Ticker with Gemini API

Learn how to fetch real-time crypto prices using Gemini API and display them on an ESP8266 with a vibrant TFT screen. "

IntermediateFull instructions provided2 hours169
Building Arduino Crypto Price Ticker with Gemini API

Things used in this project

Hardware components

NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
×1
tft 240x240
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Connectivity

It is SPI display despite confusing PIN labels.
This display does not have CS pin. If you purchase TFT screen it is better to get one that provides CS pin

Code

Displaying pricefeed for 5 selected crypto curriencies in plain format on TFT display

Arduino
This code already contains a workaround to trim JSON response to just the currency pairs we are intererested in.
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#include <ArduinoJson.h>

const char* ssid = "*******";
const char* password = "*******";
const char* serverName = "api.gemini.com";

// Define the WiFi client
WiFiClientSecure client;

#define TFT_DC    D1     // TFT DC pin 
#define TFT_RST   D2     // TFT RST pin 
#define TFT_CS    D8     // TFT CS pin 

// Initialize ST7789 TFT library with hardware SPI module
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  Serial.begin(9600);  
  // Connect to Wi-Fi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }

  Serial.println(""); Serial.println("WiFi connected");
  Serial.println("IP address: "); Serial.println(WiFi.localIP());
  
  // Initialize the display
  tft.init(240, 240, SPI_MODE2);
  tft.setRotation(0);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextWrap(true);
  tft.setTextColor(ST77XX_WHITE);
  tft.setTextSize(1);
  tft.print("hello");
}


void loop() {
  client.setInsecure(); // Use this line if you don't have the SSL fingerprint and want to ignore SSL verification
  
  if (client.connect(serverName, 443)) { // Port 443 for HTTPS
    String url = "/v1/pricefeed";
    
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + serverName + "\r\n" + 
                 "Connection: close\r\n\r\n");

    unsigned long timeout = millis();
    while (client.available() == 0) {
      if (millis() - timeout > 5000) {
        Serial.println(">>> Client Timeout !");
        client.stop();
        return;
      }
    }

    // Skip HTTP headers
    while (client.available()) {
      String line = client.readStringUntil('\r');
      if (line == "\n" || line == "\r\n") {
        break;
      }
    }

  String payload = "[";
    bool isFirst = true;
    while (client.available()) {
      String chunk = client.readStringUntil('\n');
      int start = 0, end = 0;

      // Find and extract necessary parts
      while ((start = chunk.indexOf("{\"pair\":\"", end)) != -1) {
        end = chunk.indexOf("}", start) + 1;
        String pairData = chunk.substring(start, end);

        if (pairData.indexOf("BTCUSD") != -1 ||
            pairData.indexOf("ETHUSD") != -1 ||
            pairData.indexOf("XRPUSD") != -1 ||
            pairData.indexOf("SOLUSD") != -1 ||
            pairData.indexOf("DOTUSD") != -1) {
          if (!isFirst) {
            payload += ",";
          }
          payload += pairData;
          isFirst = false;
        }
      }
    }
    payload += "]";
    Serial.print(payload);

    // Debug: Print the payload to Serial Monitor
    //Serial.println(payload);
  
    // Parse JSON data
    StaticJsonDocument<8192> doc;
    DeserializationError error = deserializeJson(doc, payload);
    
    if (error) {
      Serial.print("deserializeJson() failed: ");
      Serial.println(error.f_str());
      return;
    }
  
    // Display data on the TFT screen
    tft.fillScreen(ST77XX_BLACK); // Clear screen before displaying new data
    int y = 10;
    for (size_t i = 0; i < doc.size(); i++) {
      const char* pair = doc[i]["pair"];
      if (strcmp(pair, "BTCUSD") == 0 || 
          strcmp(pair, "ETHUSD") == 0 || 
          strcmp(pair, "GALUSD") == 0) {
        const char* price = doc[i]["price"];
        const char* percentChange24h = doc[i]["percentChange24h"];

        // Display pair
        tft.setCursor(10, y); 
        tft.print(pair);
        // Display price
        tft.setCursor(80, y); 
        tft.print(price);
        // Display percent change
        tft.setCursor(160, y); 
        tft.print(percentChange24h);
        y += 20;
      }
    }
  } else {
    Serial.println("Connection to API server failed.");
  }
  
  delay(3000);
}

Display pricefeed for 5 selected crypto currencies formated

Arduino
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#include <ArduinoJson.h>

#define ST77XX_GRAY 0x8410 // Define a gray color
String timestamp;

const char* ssid = "Dziubym";
const char* password = "Mikigosia1";
const char* serverName = "api.gemini.com";

// Define the WiFi client
WiFiClientSecure client;

#define TFT_DC    D1     // TFT DC pin 
#define TFT_RST   D2     // TFT RST pin 
#define TFT_CS    D8     // TFT CS pin 

// Initialize ST7789 TFT library with hardware SPI module
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  Serial.begin(9600);  
  // Connect to Wi-Fi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }

  Serial.println(""); Serial.println("WiFi connected");
  Serial.println("IP address: "); Serial.println(WiFi.localIP());
  
  // Initialize the display
  tft.init(240, 240, SPI_MODE2);
  tft.setRotation(1);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextWrap(true);
  tft.setTextColor(ST77XX_WHITE);
  tft.setTextSize(2);  
  tft.fillRect(0, 5, 50, 25, 0x505050);   // First box
  tft.fillRect(60, 5, 105, 25, 0x505050);  // Second box
  tft.fillRect(175, 5, 65, 25, 0x505050); // Third box 
  tft.setCursor(5,10);
  tft.print("ID   PRICE     %CHG");
  
}

void loop() {
  client.setInsecure(); // turn off SSL verification
  if (client.connect(serverName, 443)) { 
    
    String url = "/v1/pricefeed";
    
    client.print(String("GET ") + url + " HTTP/1.1\r\n" +
                 "Host: " + serverName + "\r\n" + 
                 "Connection: close\r\n\r\n");

    unsigned long timeout = millis();
    while (client.available() == 0) {
      if (millis() - timeout > 5000) {
        Serial.println(">>> Client Timeout !");
        client.stop();
        return;
      }
    }
    
    //Skip HTTP headers
    while (client.available()) {
      String line = client.readStringUntil('\r');
      if (line.indexOf("Date") != -1) timestamp = reformatDateString(line);
      if (line == "\n" || line == "\r\n") {
        break;
      }
    }
    // Read the JSON response

    String payload = "[";
    bool isFirst = true;
    while (client.available()) {
      String chunk = client.readStringUntil('\n');
      int start = 0, end = 0;

      // Find and extract necessary parts
      while ((start = chunk.indexOf("{\"pair\":\"", end)) != -1) {
        end = chunk.indexOf("}", start) + 1;
        String pairData = chunk.substring(start, end);

        if (pairData.indexOf("BTCUSD") != -1 ||
            pairData.indexOf("ETHUSD") != -1 ||
            pairData.indexOf("XRPUSD") != -1 ||
            pairData.indexOf("SOLUSD") != -1 ||
            pairData.indexOf("DOTUSD") != -1) {
          if (!isFirst) {
            payload += ",";
          }
          payload += pairData;
          isFirst = false;
        }
      }
    }
    payload += "]";
 // Serial.print(payload);
  displayPricefeed(payload);
  }
  delay(3000);
} 

// Function to round a string representation of a float to two decimal places
String roundToTwoDecimalPlaces(const char* value) {
  float num = atof(value);
  char buffer[10];
  dtostrf(num, 4, 2, buffer);
  return String(buffer);
}

String convertToPercentage(String value) {
    // Convert the string to a float
    float floatValue = value.toFloat();

    // Multiply by 100 to get the percentage
    float percentage = floatValue * 100;

    // Create a string representation with two decimal places
    char buffer[7]; // Allows for "-0.00" (6 characters)
    dtostrf(percentage, 6, 2, buffer);

    // Remove trailing spaces
    String result = String(buffer);
    result.trim();

    return result;
}


String reformatDateString(String dateString) {
  // Remove '\r' if it is at the end of the string
  if (dateString.endsWith("\r")) {
    dateString.remove(dateString.length() - 1);
  }

  // Example input: "Date: Sun, 30 Jun 2024 09:16:31 GMT"
  // Desired output: "2024-06-30 09:16:31"

  // Extract components from the date string
  int dayStart = dateString.indexOf(',') + 2;
  String day = dateString.substring(dayStart, dayStart + 2);

  int monthStart = dayStart + 3;
  String monthStr = dateString.substring(monthStart, monthStart + 3);

  int yearStart = monthStart + 4;
  String year = dateString.substring(yearStart, yearStart + 4);

  int timeStart = yearStart + 5;
  String time = dateString.substring(timeStart, timeStart + 8);

  // Convert month abbreviation to number
  String month;
  if (monthStr == "Jan") month = "01";
  else if (monthStr == "Feb") month = "02";
  else if (monthStr == "Mar") month = "03";
  else if (monthStr == "Apr") month = "04";
  else if (monthStr == "May") month = "05";
  else if (monthStr == "Jun") month = "06";
  else if (monthStr == "Jul") month = "07";
  else if (monthStr == "Aug") month = "08";
  else if (monthStr == "Sep") month = "09";
  else if (monthStr == "Oct") month = "10";
  else if (monthStr == "Nov") month = "11";
  else if (monthStr == "Dec") month = "12";

  // Format the new date string
  String compactDateString = year + "-" + month + "-" + day + " " + time;

  return compactDateString;
}


void  displayPricefeed(String JSONdata){


// Parse JSON data
  StaticJsonDocument<8192> doc; // Parse JSON data
  deserializeJson(doc, JSONdata);
  // Display data on the TFT screen
  int y=50;
  tft.fillRect(0, 40, 50, 120, ST77XX_GRAY);   // First box
  tft.fillRect(60, 40, 105, 120, ST77XX_GRAY);  // Second box
  tft.fillRect(175, 40, 65, 120, ST77XX_GRAY); // Third box
  for (size_t i = 0; i < doc.size(); i++) {
    const char* pair = doc[i]["pair"];
    if (strcmp(pair, "BTCUSD") == 0 || 
        strcmp(pair, "ETHUSD") == 0 || 
        strcmp(pair, "SOLUSD") == 0 || 
        strcmp(pair, "XRPUSD") == 0 || 
        strcmp(pair, "DOTUSD") == 0) {

      const char* price = doc[i]["price"];
      const char* percentChange24h = doc[i]["percentChange24h"];
      
 // Remove currency characters
      String symbol = String(pair).substring(0, 3);

      // Round price and percentage to two decimal places
      String roundedPrice = roundToTwoDecimalPlaces(price);
      String roundedPercentChange = convertToPercentage(percentChange24h);
 


      // Display symbol
      tft.setTextColor(ST77XX_WHITE);
      tft.setCursor(10, y);
      tft.print(symbol);

    
      // Set color based on percent change
      float percentChange = atof(percentChange24h);
      if (percentChange >= 0) {
        tft.setTextColor(ST77XX_GREEN);
      } else {
        tft.setTextColor(ST77XX_RED);
      }

      char buffer[50];
       
      // Display price
      tft.setCursor(65, y); // Move one character to the left (assuming each character is about 5 pixels wide)
      sprintf(buffer, "%8s", roundedPrice.c_str());
      tft.print(buffer);

      // Display percent change
      tft.setCursor(175, y);
      sprintf(buffer, "%5s", roundedPercentChange.c_str());
      tft.print(buffer);
      y=y+22;
    }
  }
  tft.fillRect(0, 180, 240, 30, ST77XX_BLACK);   // First box
  tft.setTextColor(ST77XX_GRAY);
  tft.setCursor(0, y+30);
  tft.print(timestamp);
}

Credits

mdraber
50 projects • 74 followers
Contact

Comments

Please log in or sign up to comment.