script updated to work with openapiv1.coinstats.app
motechsolutions opened this issue · 0 comments
motechsolutions commented
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()
{
}