ESP32 HTTPS example / Bitcoin monitor /CYD

ESP32 HTTPS example / Bitcoin monitor

This is a very simple example of an ESP32 application using HTTPS to extract data via an API on a remote server. This simple configuration cannot, however, validate the authenticity of the remote server without additional steps. But for many non-sensitive applications, this is OK.

It uses the ESP32 library WiFiClientSecure.h for enabling https://xxxxx.yyy URLs.

As a demonstration I have used APIs from two different Bitcoin providers which appear to require HTTPS, not simple HTTP, and seem to work without any other authentication mechanism. That is you do not need any account, API key or give credit card details etc.

I've wrapped up the demonstation for a Cheap Yellow Display (CYD) but, with some hacking, it can have other applications. It is un-polished but works.

Disclaimer: I don't hold any Bitcoins and am using this as a basic test before I use a similar technique to extract data from another site.

Maybe someone finds it interesting or useful.

/*
  bitcoin_monitor for Cheap Yellow Display

  Demonstrates Minimal HTTPS example  for talking to an https:// web site with encryption
  but without any of the normal checks for validating the remote server

  Demonstrates two APIs that appear to need no account or authorisation key:
  coingecko and coinbase
  
  
 
  Instructions:

  01 Change WLAN credentials
  02 Compile for ESP32 Dev Module




  See:
  https://www.hackster.io/etolocka/bitcoin-monitor-with-crowpanel-5-79-and-gxepd2-1c5f3e
  https://randomnerdtutorials.com/esp32-https-requests/
  https://docs.coingecko.com/v3.0.1/reference/authentication
  https://docs.cdp.coinbase.com/api-reference/v2/introduction


  PINOUT CYD
  See: https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/PINS.md


  TFT (Uses the HSPI)
  IO2 	TFT_RS 	  AKA: TFT_DC
  IO12 	TFT_SDO 	AKA: TFT_MISO
  IO13 	TFT_SDI 	AKA: TFT_MOSI
  IO14 	TFT_SCK 	
  IO15 	TFT_CS 	
  IO21 	TFT_BL 	  Backlight Also on P3 connector, for some reason


  Ver 0.04P  16.02.2026  integrate CYD screen based on ST7789_cyd_V0_04.ino (compile for ESP32)

  Author 6v6gt 
*/


#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#include "SPI.h"
#include "Adafruit_GFX.h"
// #include "Adafruit_ILI9341.h"
#include <Adafruit_ST7789.h>

#define TFT_CS 15
#define TFT_RST -1
#define TFT_DC 2

SPIClass hspi = SPIClass(HSPI);
Adafruit_ST7789 tft = Adafruit_ST7789(&hspi, TFT_CS, TFT_DC, TFT_RST);  //  Adafruit_ST7789(SPIClass *spiClass, int8_t cs, int8_t dc, int8_t rst);


// Change the following parameters to suit your environment
const char* ssid = "your own SSIS";
const char* password = "your own password";

String serverPath1 = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd,chf";
String serverPath2 = "https://api.coinbase.com/v2/prices/BTC-USD/spot";


WiFiClientSecure* client = new WiFiClientSecure;


void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 4000) delay(100);  // Wait for Serial Monitor before continuing
  Serial.println("\nStarting . . . ");

  // tft
  pinMode(21, OUTPUT);  // backlight CYD
  digitalWrite(21, HIGH);
  //tft.begin();
  tft.init(240, 320);  // Init ST7789 240x240
  tft.setRotation(1);
  tft.invertDisplay(false);

  // jsonBuffer.reserve(40960);  // est only 16k - overkill - should use a stream method.

  // set up WiFi
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WLAN");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Wifi RSSI=");
  Serial.println(WiFi.RSSI());

}

void loop() {
  
  // Check WiFi connection status
  if (WiFi.status() == WL_CONNECTED) {


    // WiFiClientSecure* client = new WiFiClientSecure;
    if (client) {
      // set secure client without certificate
      client->setInsecure();
      //create an HTTPClient instance
      HTTPClient https;

      //Initializing an HTTPS communication using the secure client
      Serial.print("\n[HTTPS] begin...\n");

      
      // =========================
      // coingecko +  tft print
      // =========================
      if (https.begin(*client, serverPath1)) {  // HTTPS
        Serial.print(">>>coingecko\n");
        Serial.print("[HTTPS] GET...\n");
        // start connection and send HTTP header
        int httpCode = https.GET();
        // httpCode will be negative on error
        if (httpCode > 0) {
          // HTTP header has been send and Server response header has been handled
          Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
          // file found at server
          if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
            // print server response payload
            String payload = https.getString();
            Serial.println(payload);

            JsonDocument doc;
            DeserializationError error = deserializeJson(doc, payload);
            if (!error) {
              float priceUSD = doc["bitcoin"]["usd"];
              Serial.print("Bitcoin price USD : ") ;
              Serial.println( priceUSD ) ;

              // tft
              tft.fillScreen(ST77XX_BLACK);
              tft.setCursor(10, 20);
              tft.setTextSize(3);
              tft.setTextColor(ST77XX_YELLOW, ST77XX_BLUE);
              tft.println( "Bitcoin" ) ;

              tft.setCursor(10, 50);
              tft.setTextSize(4);
              tft.setTextColor(ST77XX_WHITE, ST77XX_BLACK);  
              tft.print("$") ;
              tft.println( priceUSD ) ;

              float priceEuro = doc["bitcoin"]["chf"];
              Serial.print("Bitcoin price CHF : ") ;
              Serial.println( priceEuro ) ;
            }
            else {
              Serial.println( "ERROR deserializeJson(doc, payload)" ) ;
            }
          }
        } else {
          Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
        }
        https.end();
      } // if (https.begin)

      // =========================
      // coinbase
      // =========================
      if (https.begin(*client, serverPath2)) {  // HTTPS
        Serial.print(">>>coinbase\n");
        Serial.print("[HTTPS] GET...\n");
        // start connection and send HTTP header
        int httpCode = https.GET();
        // httpCode will be negative on error
        if (httpCode > 0) {
          // HTTP header has been send and Server response header has been handled
          Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
          // file found at server
          if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
            // print server response payload
            String payload = https.getString();
            Serial.println(payload);

            JsonDocument doc;
            DeserializationError error = deserializeJson(doc, payload);
            if (!error) {
              float priceUSD = doc["data"]["amount"];
              Serial.print("Bitcoin price USD : ") ;
              Serial.println( priceUSD ) ;

            }
            else {
              Serial.println( "ERROR deserializeJson(doc, payload)" ) ;
            }
          }
        } else {
          Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
        }
        https.end();
      } // if (https.begin)




    } // if (client)
  }

  delay(60000);  // 1 minute. not too often or the API may block
  Serial.println("") ;

  
}  // loop

3 Likes

Hi @6v6gt ,

Thank you for sharing!
I think it will be very useful as a template for REST APIs.

So, I tried out GitHub's REST API that I chose as follows:

The code gets the number of downloads for Arduino IDE 2.3.7.

Since the response size is over 20KB, I expanded the deserialization method to two options:

  • Allocate JsonDocument from heap memory
  • Read from https stream in chunks
/*
  GitHub assets monitor for Cheap Yellow Display

  Demonstrates Minimal HTTPS example  for talking to an https:// web site with encryption
  but without any of the normal checks for validating the remote server

  Demonstrates two APIs that appear to need no account or authorisation key:
  coingecko and coinbase

  Posted in:
  https://forum.arduino.cc/t/esp32-https-example-bitcoin-monitor-cyd/1431111

  Instructions:

  01 Change WLAN credentials
  02 Compile for ESP32 Dev Module


  See:
  https://www.hackster.io/etolocka/bitcoin-monitor-with-crowpanel-5-79-and-gxepd2-1c5f3e
  https://randomnerdtutorials.com/esp32-https-requests/
  https://docs.github.com/en/rest
  https://docs.github.com/en/rest/releases


  PINOUT CYD
  See: https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/PINS.md


  TFT (Uses the HSPI)
  IO2 	TFT_RS 	  AKA: TFT_DC
  IO12 	TFT_SDO 	AKA: TFT_MISO
  IO13 	TFT_SDI 	AKA: TFT_MOSI
  IO14 	TFT_SCK 	
  IO15 	TFT_CS 	
  IO21 	TFT_BL 	  Backlight Also on P3 connector, for some reason


  Ver 0.04P  16.02.2026  integrate CYD screen based on ST7789_cyd_V0_04.ino (compile for ESP32)

  Author 6v6gt
*/


#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

// Set the method to handle large JSON data
// true:  Allocate JsonDocument from heap memory
// false: Deserialize the stream in chunks
#define USE_HEAP_ALLCATOR true

// https://arduinojson.org/v7/how-to/use-external-ram-on-esp32/
#if USE_HEAP_ALLCATOR
struct HeapAllocator : ArduinoJson::Allocator {
  void* allocate(size_t size) override {
    return heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
  }
  void deallocate(void* pointer) override {
    heap_caps_free(pointer);
  }
  void* reallocate(void* ptr, size_t new_size) override {
    return heap_caps_realloc(ptr, new_size, MALLOC_CAP_DEFAULT);
  }
};
#endif // USE_HEAP_ALLCATOR

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
//#include <Adafruit_ST7789.h>

#define TFT_DC    2
#define TFT_MISO  12
#define TFT_MOSI  13
#define TFT_CLK   14
#define TFT_CS    15
#define TFT_RST   -1
#define TFT_BL    21

SPIClass hspi = SPIClass(HSPI);
#ifdef _ADAFRUIT_ILI9341H_
#define TFT_WHITE ILI9341_WHITE
#define TFT_BLACK ILI9341_BLACK
Adafruit_ILI9341 tft = Adafruit_ILI9341(&hspi, TFT_DC, TFT_CS, TFT_RST);
#else
#define TFT_WHITE ST77XX_WHITE
#define TFT_BLACK ST77XX_BLACK
Adafruit_ST7789  tft = Adafruit_ST7789 (&hspi, TFT_CS, TFT_DC, TFT_RST);
#endif

// Change the following parameters to suit your environment
const char* ssid = "your own SSIS";
const char* password = "your own password";

// Get the latest release: /repos/{owner}/{repo}/releases/latest
static const String serverPath = "https://api.github.com/repos/" "arduino" "/" "arduino-ide" "/releases/latest";

WiFiClientSecure *client = new WiFiClientSecure;

void setup() {
  Serial.begin(115200);
  while (millis() < 1000);  // Wait for Serial Monitor before continuing
  Serial.println("\nStarting . . . ");

  // tft
  pinMode(TFT_BL, OUTPUT);  // backlight CYD
  digitalWrite(TFT_BL, HIGH);

#ifdef _ADAFRUIT_ILI9341H_
  tft.begin();              // Init ILI9341
#else
  tft.init(240, 320);       // Init ST7789 240x240
#endif

  tft.invertDisplay(false);
  tft.setRotation(1);
  tft.setTextSize(1);
  tft.setTextColor(TFT_WHITE);

  // jsonBuffer.reserve(40960);  // est only 16k - overkill - should use a stream method.

  // set up WiFi
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WLAN");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to WiFi network with IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Wifi RSSI=");
  Serial.println(WiFi.RSSI());
}

void loop() {
  // Check WiFi connection status
  if (WiFi.status() == WL_CONNECTED) {

    // WiFiClientSecure* client = new WiFiClientSecure;
    if (client) {
      // set secure client without certificate
      client->setInsecure();
      //create an HTTPClient instance
      HTTPClient https;

      //Initializing an HTTPS communication using the secure client
      Serial.print("\n[HTTPS] begin...\n");

      // =========================
      // GET REST API +  tft print
      // =========================
      if (https.begin(*client, serverPath)) {  // HTTPS
        tft.setCursor(0, 0);
        tft.fillScreen(TFT_BLACK);
        Serial.print("[HTTPS] GET...\n");

        // start connection and send HTTP header
        int httpCode = https.GET();
        // httpCode will be negative on error
        if (httpCode > 0) {
          // HTTP header has been send and Server response header has been handled
          Serial.printf("[HTTPS] GET... code: %d\n", httpCode);

          // file found at server
          if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
#if USE_HEAP_ALLCATOR
            // https://arduinojson.org/v7/how-to/deserialize-a-very-large-document/
            JsonDocument filter;
            filter["assets"][0]["name"] = true;
            filter["assets"][0]["download_count"] = true;

            String payload = https.getString();
            //Serial.println(payload);

            HeapAllocator allocator;
            JsonDocument doc(&allocator);
            DeserializationError error = deserializeJson(doc, payload, DeserializationOption::Filter(filter));
            if (!error) {
              for (auto arr : doc["assets"].as<JsonArray>()) {
                const char *name = arr["name"];
                const long count = arr["download_count"];
                Serial.printf("%s: %d\n", name, count);
                tft.printf   ("%s: %d\n", name, count);
              }
            } else {
              const char* fmt = "deserializeJson() failed: %s\n";
              const char* msg = error.c_str();
              Serial.printf(fmt, msg);
              tft.printf   (fmt, msg);
            }
#else
            // https://arduinojson.org/v7/how-to/deserialize-a-very-large-document/
            JsonDocument filter;
            filter["name"] = true;
            filter["download_count"] = true;

            // https://docs.arduino.cc/language-reference/en/functions/communication/stream/
            // https://github.com/espressif/arduino-esp32/blob/master/libraries/HTTPClient/src/HTTPClient.h#L252
            Stream &stream = https.getStream();
            stream.find("\"assets\":[");
            do {
              JsonDocument doc;
              DeserializationError error = deserializeJson(doc, stream, DeserializationOption::Filter(filter));
              if (!error) {
                const char *name = doc["name"];
                const long count = doc["download_count"];
                Serial.printf("%s: %d\n", name, count);
                tft.printf   ("%s: %d\n", name, count);
              } else {
                const char* fmt = "deserializeJson() failed: %s\n";
                const char* msg = error.c_str();
                Serial.printf(fmt, msg);
                tft.printf   (fmt, msg);
                serializeJson(doc, Serial); Serial.println();
                break; // can't continue to deserialize
              }
            } while (stream.findUntil(",", "]"));
#endif // USE_HEAP_ALLCATOR
          }
        }

        else {
          const char* fmt = "[HTTPS] GET... failed, error: %s\n";
          const char* msg = https.errorToString(httpCode).c_str();
          Serial.printf(fmt, msg);
          tft.printf   (fmt, msg);
        }

        https.end();
      }  // if (https.begin)
    }    // if (client)
  }

  delay(120000);  // The primary rate limit for unauthenticated requests is 60 requests per hour.
  Serial.println("");
}  // loop

Sorry for the messy code!

2 Likes

Thanks very much for posting the code.

I have used the setInsecure() function on an ESP8266 as well, and find it to be extremely useful. As I understand it, it's still an encrypted HTTPS session on port 443, but my end just doesn't bother to verify the server's certificate. So as the internet moves to shorter and shorter-life certificates, I don't have to do anything when a certificate is replaced. My device doesn't need to know date/time either. I wouldn't use it for my bank, but for most of the stuff we do, it's fine.

1 Like

I'm glad that it was appreciated. Where I have benefited from the contribution of others I have acknowledged this by a link in the code. In this case, mainly "Random Nerd".

I have only recently become aware that this was a possibility, that it accessing an https:// link from "Arduino" without a huge amount of messing about with certificates. Apart from allowing access to a large number of URLs which initially insist on https:// from the start, or immediately redirect to https://, you also get an encrypted data stream. Of course, you are not protected against a server which impersonates your chosen destination server, but as long as you understand the risks it is OK. If all you can expose is say an API key to a weather application, then I'd say that risk is manageable (negligible) but maybe not where important credentials could be extracted.

It is often useful to be able to display a "tiny, selected" corner of the internet on a small screen. For published interfaces it is normally OK but for general screen scraping, the Arduino platform is not optimal. The client doesn't exhibit java script capabilities so it often doesn't see much and tools/resources, which would be useful to pick out data buried deep in html, aren't really there or sufficient.