GraxCode/lilygo_t5_4.7_crypto

script updated to work with openapiv1.coinstats.app

motechsolutions opened this issue · 0 comments

For anyone using this script on a LilyGo 4.7 e-ink display and has now found the api.coinstats.app API does not work, then you can use my updated code (fixed and updated with lots of help from ChatGPT)

#ifndef BOARD_HAS_PSRAM
#error "Please enable PSRAM !!!"
#endif
#define WIFI_OFF WIFI_MODE_NULL
#define WIFI_STA WIFI_MODE_STA

#define WHITE 0xFF
#define LGREY 0xBB
#define GREY 0x88
#define DGREY 0x44
#define BLACK 0x00

#include <Arduino.h>
#include <esp_task_wdt.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "epd_driver.h"
#include "esp_adc_cal.h"
#include <Wire.h>
#include <SPI.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <WiFi.h>
#include <time.h>

#include "roboto8.h"
#include "roboto10.h"
#include "roboto12.h"
#include "roboto18.h"
#include "roboto18r.h"
#include "roboto24r.h"

#define BATT_PIN 36
enum alignment
{
    LEFT,
    RIGHT,
    CENTER
};

const char *ssid = "YOUR_SSID";
const char *password = "YOUR_PASSWRD";

/*
 * Keep this at 4. The long currency name is used for the api, so check the spelling.
 */
const int AMOUNT_CURR = 4;
const String currency_short[] = {"BTC", "ETH", "XRP", "DOGE"};
const String currency_names[] = {"bitcoin", "ethereum", "matic-network", "dogecoin"};
const String display_names[] = {"Bitcoin", "Ethereum", "Polygon", "Dogecoin"};

int currency_index;

uint8_t *framebuffer;
int vref = 1100;

int wifi_signal;

uint8_t start_wifi()
{
    Serial.println("Connecting to: " + String(ssid));
    IPAddress dns(8, 8, 8, 8);
    WiFi.disconnect();
    WiFi.mode(WIFI_STA); // switch off AP
    WiFi.setAutoConnect(true);
    WiFi.setAutoReconnect(true);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED)
    {
        Serial.println("STA: Failed!");
        WiFi.disconnect(false);
        delay(500);
        WiFi.begin(ssid, password);
    }
    if (WiFi.status() == WL_CONNECTED)
    {
        wifi_signal = WiFi.RSSI(); // Get Wifi Signal strength now, because the WiFi will be turned off to save power!
        Serial.println("WiFi connected as " + WiFi.localIP().toString());
    }
    else
        Serial.println("WiFi connection failed: " + String(WiFi.status()));
    return WiFi.status();
}

void stop_wifi()
{
    WiFi.disconnect();
    WiFi.mode(WIFI_OFF);
}

float c_price = -1.0f;
float c_price_btc = -1.0f;
float c_price_24h = 0.0f;
float c_price_1h = 0.0f;

int c_rank = 0;

double c_cap = -1.0;
double c_vol = -1.0;

/**
 * Read https://api.coinstats.app API data into fields
 **/
bool decode_web_data(WiFiClient &json) {
    DynamicJsonDocument doc(4 * 1024);
    DeserializationError error = deserializeJson(doc, json);
    if (error) {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
        return false;
    }

    // Accessing individual fields from JSON object
    const char* id = doc["id"];
    c_price = doc["price"];
    c_price_btc = doc["priceBtc"];
    c_rank = doc["rank"];
    c_cap = doc["marketCap"];
    c_vol = doc["volume"];
    c_price_1h = doc["priceChange1h"];
    c_price_24h = doc["priceChange1d"];

    return true;
}

/**
 * https://api.coinstats.app certificate chain.
 **/
const char* root_ca =
"-----BEGIN CERTIFICATE-----\n" \
"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\n" \
"RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\n" \
"VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\n" \
"DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\n" \
"ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\n" \
"VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\n" \
"mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\n" \
"IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\n" \
"mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" \
"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\n" \
"dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\n" \
"jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\n" \
"BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\n" \
"DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\n" \
"9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\n" \
"jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\n" \
"Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\n" \
"ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" \
"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" \
"-----END CERTIFICATE-----\n";

String apikey= "YOUR openapi.coinstats.app API KEY"; 

bool read_web_data() {
    HTTPClient http;
    String cur = currency_names[currency_index];
    cur.toLowerCase();
    String uri = "/coins/" + cur + "?currency=GBP";
    
    // Specify the API endpoint and root CA certificate
    http.begin("https://openapiv1.coinstats.app" + uri, root_ca);
    
    // Add the API key to the request headers
    http.addHeader("X-API-KEY", apikey);

    int httpCode = http.GET();
    bool success = false;
    if (httpCode == HTTP_CODE_OK) {
        // Parse the JSON data
        if (decode_web_data(http.getStream()))
            success = true;
        else
            Serial.println("Web data decoding failed");
    } else {
        Serial.printf("Connection to API failed, error: %s, HTTP code: %d\r\n", http.errorToString(httpCode).c_str(), httpCode);
        // Print out additional error details if available
        if (httpCode > 0) {
            Serial.println(http.getString());
        }
    }
    http.end();
    return success;
}

void draw_str(const GFXfont font, const String txt, int x, int y, alignment align)
{
    const char *string = (char *)txt.c_str();
    int x1, y1;
    int w, h;
    int xx = x, yy = y;
    get_text_bounds(&font, string, &xx, &yy, &x1, &y1, &w, &h, NULL);
    if (align == RIGHT)
        x = x - w;
    if (align == CENTER)
        x = x - w / 2;
    int cursor_y = y + h;
    writeln(&font, string, &x, &cursor_y, framebuffer);
}

void start_deep_sleep()
{
    epd_poweroff_all();
    esp_sleep_enable_timer_wakeup(1000L * 60000L * 5L); // sleep 5 min (API limit)

    Serial.println("Starting deep-sleep period...");

    esp_deep_sleep_start();
}

void draw_framebuf(bool clear_buf)
{
    epd_draw_grayscale_image(epd_full_screen(), framebuffer);
    if (clear_buf)
    {
        memset(framebuffer, 0xFF, EPD_WIDTH * EPD_HEIGHT / 2);
    }
}

void correct_adc_ref_volt()
{
    esp_adc_cal_characteristics_t adc_chars;
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
    if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF)
    {
        Serial.printf("eFuse Vref:%u mV\r\n", adc_chars.vref);
        vref = adc_chars.vref;
    }
}
void setup()
{
    Serial.begin(115200);

    correct_adc_ref_volt();
    epd_init();

    framebuffer = (uint8_t *)ps_calloc(sizeof(uint8_t), EPD_WIDTH * EPD_HEIGHT / 2);
    if (!framebuffer)
    {
        Serial.println("alloc memory failed.");
        while (1)
            ;
    }
    memset(framebuffer, 0xFF, EPD_WIDTH * EPD_HEIGHT / 2);

    epd_poweron();
    epd_clear();

    uint16_t v = analogRead(BATT_PIN);
    float battery_voltage = ((float)v / 4095.0) * 2.0 * 3.3 * (vref / 1000.0);
    String voltage = String(battery_voltage) + "V";
    Serial.println(voltage);

    int x_pos = (int)(EPD_WIDTH * 0.02f);
    int y_pos = (int)(EPD_HEIGHT * 0.96f);

    draw_str(Roboto8, (char *)voltage.c_str(), x_pos, y_pos, LEFT);

    x_pos = (int)(EPD_WIDTH * 0.98f);

    draw_str(Roboto8, "https://github.com/GraxCode - v1.0", x_pos, y_pos, RIGHT);

    x_pos = (int)(EPD_WIDTH * 0.5f);
    y_pos = (int)(EPD_HEIGHT * 0.45f);
    draw_str(Roboto24R, "Connecting to " + String(ssid), x_pos, y_pos, CENTER);

    draw_framebuf(true);
    if (start_wifi() != WL_CONNECTED)
    {
        epd_clear();
        draw_str(Roboto24R, "WLAN failed. Check credentials.", x_pos, y_pos, CENTER);
    }
    else
    {
        epd_clear();
        for (currency_index = 0; currency_index < AMOUNT_CURR; currency_index++)
        {
            int y_off = (currency_index / float(AMOUNT_CURR)) * EPD_HEIGHT;
            int y_off_end = ((currency_index + 1) / float(AMOUNT_CURR)) * EPD_HEIGHT;

            epd_draw_line(0, y_off_end, EPD_WIDTH, y_off_end, BLACK, framebuffer);

            x_pos = (int)(EPD_WIDTH * 0.02f);
            y_pos = (int)(EPD_HEIGHT * 0.04f);

            draw_str(Roboto24R, (char *)display_names[currency_index].c_str(), x_pos, y_off + y_pos, LEFT);
            y_pos = (int)(EPD_HEIGHT * 0.15f);
            draw_str(Roboto18R, (char *)currency_short[currency_index].c_str(), x_pos, y_off + y_pos, LEFT);
            if (!read_web_data())
            {
                x_pos = (int)(EPD_WIDTH * 0.5f);
                y_pos = (int)(EPD_HEIGHT * 0.1f);
                draw_str(Roboto18R, "Wifi read failed", x_pos, (y_off + y_off_end) / 2, CENTER);
            }
            else
            {
                x_pos = (int)(EPD_WIDTH * 0.30f);
                y_pos = (int)(EPD_HEIGHT * 0.05f);
                draw_str(Roboto18R, String(c_price, 2) + " GBP", x_pos, y_off + y_pos, LEFT);
                y_pos = (int)(EPD_HEIGHT * 0.135f);
                draw_str(Roboto12, "= " + String(c_price_btc, 8) + " BTC", x_pos, y_off + y_pos, LEFT);
                y_pos = (int)(EPD_HEIGHT * 0.2f);
                draw_str(Roboto10, "RANK #" + String(c_rank), x_pos, y_off + y_pos, LEFT);

                x_pos = (int)(EPD_WIDTH * 0.59f);
                y_pos = (int)(EPD_HEIGHT * 0.05f);
                draw_str(abs(c_price_24h) > 10 ? Roboto18R : Roboto18, String(c_price_24h, 2) + "%", x_pos, y_off + y_pos, LEFT);
                y_pos = (int)(EPD_HEIGHT * 0.135f);
                draw_str(Roboto12, "24h CHANGE", x_pos, y_off + y_pos, LEFT);
                y_pos = (int)(EPD_HEIGHT * 0.2f);
                draw_str(Roboto10, String(c_cap / 1000000.0f, 2) + " M CAP", x_pos, y_off + y_pos, LEFT);

                x_pos = (int)(EPD_WIDTH * 0.94f);
                y_pos = (int)(EPD_HEIGHT * 0.05f);
                draw_str(abs(c_price_1h) > 5 ? Roboto18R : Roboto18, String(c_price_1h, 2) + "%", x_pos, y_off + y_pos, RIGHT);
                y_pos = (int)(EPD_HEIGHT * 0.135f);
                draw_str(Roboto12, "1h CHANGE", x_pos, y_off + y_pos, RIGHT);
                y_pos = (int)(EPD_HEIGHT * 0.2f);
                draw_str(Roboto10, String(c_vol / 1000000.0f, 2) + " M VOL", x_pos, y_off + y_pos, RIGHT);

                // min to 100%, put in range 0 - 255
                int intensity = (int)(255.0f - (min(abs(c_price_1h) + 2.0f, 10.0f) / 10.0f) * 255.0f);
                int left_x = (int)(EPD_WIDTH * 0.96f);
                int right_x = (int)(EPD_WIDTH * 0.99f);
                int mid_x = (left_x + right_x) / 2;

                int low_y_up = y_off + (int)(EPD_HEIGHT * 0.08f);
                int high_y_up = y_off + (int)(EPD_HEIGHT * 0.04f);
                int low_y_down = y_off + (int)(EPD_HEIGHT * (0.25f - 0.08f));
                int high_y_down = y_off + (int)(EPD_HEIGHT * (0.25f - 0.04f));

                if (c_price_1h > 0)
                {
                    epd_fill_triangle(left_x, low_y_up, right_x, low_y_up, mid_x, high_y_up, intensity, framebuffer);
                }
                else
                {
                    epd_fill_triangle(left_x, low_y_down, right_x, low_y_down, mid_x, high_y_down, intensity, framebuffer);
                }
                epd_draw_triangle(left_x, low_y_up, right_x, low_y_up, mid_x, high_y_up, BLACK, framebuffer);
                epd_draw_triangle(left_x, low_y_down, right_x, low_y_down, mid_x, high_y_down, BLACK, framebuffer);
            }
            draw_framebuf(true);
        }
    }
    stop_wifi();
    start_deep_sleep();
}

void loop()
{
}