Hardware components | ||||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
| |||||
Soo I was thinking of making a small crypro price tracker thats relatively easy to make and has cheap parts. To make it work, you need to put your wifi and password inside the code at the start. You also need some Esp32 libraries:
ArduinoJson — by Benoit Blanchon
ESP8266 and ESP32 OLED Driver for SSD1306 displays — by ThingPulse
The Esp32 connects to the network, updates the display every minute. The display shows a graf, percentage +/- of the price, the price of the cryptos (BTC, ETH, SOL, DOGE). You can switch the cryptos by using the BOOT button on your Esp32. Wiring: 3.3v-vcc, gnd-gnd, D19-SDA, D21-SCL
Caution: Never wire the oled while the esp32 is plugged in, it can damage and kill it.
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <SH1106Wire.h>
#include <Preferences.h>
// User config
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";
const char* COIN_IDS[] = { "bitcoin", "ethereum", "solana", "dogecoin" };
const char* COIN_SYMS[] = { "BTC", "ETH", "SOL", "DOGE" };
const int COIN_COUNT = 4;
const int BUTTON_PIN = 0; // GPIO0 boot button
const int FETCH_MS = 60000; // ms between API calls
const int HISTORY_LEN = 30; // price points to graph
const unsigned long SLEEP_MS = 300000; // 5 min inactivity sleep
//
SH1106Wire display(0x3C, 19, 21);
Preferences prefs;
float prices[4] = {0};
float changes[4] = {0};
// Price history ring buffer per coin
float history[4][30];
int histCount[4] = {0};
int histHead[4] = {0};
bool dataReady = false;
int currentCoin = 0;
unsigned long lastFetch = 0;
unsigned long lastDebounce = 0;
unsigned long lastActivity = 0;
bool lastBtn = HIGH;
// Save history to flash
void saveHistory() {
prefs.begin("ticker", false);
for (int i = 0; i < COIN_COUNT; i++) {
char key[16];
sprintf(key, "h%d", i);
prefs.putBytes(key, history[i], sizeof(float) * HISTORY_LEN);
sprintf(key, "hc%d", i);
prefs.putInt(key, histCount[i]);
sprintf(key, "hh%d", i);
prefs.putInt(key, histHead[i]);
}
prefs.end();
}
// Load history from flash
void loadHistory() {
prefs.begin("ticker", true);
for (int i = 0; i < COIN_COUNT; i++) {
char key[16];
sprintf(key, "h%d", i);
prefs.getBytes(key, history[i], sizeof(float) * HISTORY_LEN);
sprintf(key, "hc%d", i);
histCount[i] = prefs.getInt(key, 0);
sprintf(key, "hh%d", i);
histHead[i] = prefs.getInt(key, 0);
}
prefs.end();
if (histCount[0] > 0) dataReady = true;
}
// Ring buffer push
void pushHistory(int idx, float price) {
history[idx][histHead[idx]] = price;
histHead[idx] = (histHead[idx] + 1) % HISTORY_LEN;
if (histCount[idx] < HISTORY_LEN) histCount[idx]++;
}
float getHistory(int idx, int age) {
// age 0 = newest, age histCount-1 = oldest
int pos = (histHead[idx] - 1 - age + HISTORY_LEN) % HISTORY_LEN;
return history[idx][pos];
}
// Build CoinGecko URL
String buildURL() {
String ids = "";
for (int i = 0; i < COIN_COUNT; i++) {
if (i > 0) ids += "%2C";
ids += COIN_IDS[i];
}
return "https://api.coingecko.com/api/v3/simple/price?ids=" + ids +
"&vs_currencies=usd&include_24hr_change=true";
}
// Fetch prices
void fetchPrices() {
if (WiFi.status() != WL_CONNECTED) return;
HTTPClient http;
http.begin(buildURL());
http.setTimeout(8000);
int code = http.GET();
if (code == 200) {
String payload = http.getString();
DynamicJsonDocument doc(2048);
if (deserializeJson(doc, payload) == DeserializationError::Ok) {
for (int i = 0; i < COIN_COUNT; i++) {
if (doc.containsKey(COIN_IDS[i])) {
prices[i] = doc[COIN_IDS[i]]["usd"].as<float>();
changes[i] = doc[COIN_IDS[i]]["usd_24h_change"].as<float>();
pushHistory(i, prices[i]);
}
}
dataReady = true;
saveHistory();
}
}
http.end();
}
// Format price
String formatPrice(float p) {
if (p >= 10000.0f) {
char buf[12];
sprintf(buf, "$%.0f", p);
return String(buf);
} else if (p >= 1000.0f) {
char buf[12];
sprintf(buf, "$%.1f", p);
return String(buf);
} else if (p >= 1.0f) {
char buf[12];
sprintf(buf, "$%.2f", p);
return String(buf);
} else {
char buf[14];
sprintf(buf, "$%.4f", p);
return String(buf);
}
}
// Draw graph
// Graph area: x=0..127, y=28..63 (36px tall)
void drawGraph(int idx) {
int n = histCount[idx];
if (n < 2) {
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 42, "Building history...");
return;
}
// Find min/max in history
float minP = history[idx][0], maxP = history[idx][0];
for (int i = 1; i < n; i++) {
if (history[idx][i] < minP) minP = history[idx][i];
if (history[idx][i] > maxP) maxP = history[idx][i];
}
float range = maxP - minP;
if (range < 0.0001f) range = 1.0f; // avoid div by zero if flat
// Graph bounds
const int GX = 0, GY = 29, GW = 128, GH = 34;
// Draw baseline
display.drawLine(GX, GY + GH, GX + GW, GY + GH);
// Plot points oldestnewest leftright
int pts = min(n, HISTORY_LEN);
float xStep = (float)GW / (float)(pts - 1);
int prevX = -1, prevY = -1;
for (int i = 0; i < pts; i++) {
float val = getHistory(idx, pts - 1 - i); // oldest first
int px = GX + (int)(i * xStep);
int py = GY + GH - 1 - (int)((val - minP) / range * (GH - 2));
py = constrain(py, GY, GY + GH - 1);
if (prevX >= 0) {
display.drawLine(prevX, prevY, px, py);
}
prevX = px;
prevY = py;
}
// Min/max labels on right side small font
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
// Shorten labels for display width
auto shortFmt = [](float p) -> String {
if (p >= 1000.0f) {
char buf[10];
sprintf(buf, "%.1fk", p / 1000.0f);
return String(buf);
} else if (p >= 1.0f) {
char buf[10];
sprintf(buf, "%.1f", p);
return String(buf);
} else {
char buf[10];
sprintf(buf, "%.3f", p);
return String(buf);
}
};
display.drawString(128, GY - 1, shortFmt(maxP));
display.drawString(128, GY + GH - 10, shortFmt(minP));
}
// Draw full coin screen
void drawCoin(int idx) {
display.clear();
// Top bar
// Symbol
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(0, 0, COIN_SYMS[idx]);
// Price
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 4, formatPrice(prices[idx]));
// 24h change
display.setTextAlignment(TEXT_ALIGN_RIGHT);
bool up = changes[idx] >= 0;
String chg = (up ? "+" : "") + String(changes[idx], 1) + "%";
display.drawString(128, 4, chg);
// Divider
display.drawLine(0, 18, 128, 18);
// Dot indicators (between header and graph)
int dotY = 24;
int totalW = COIN_COUNT * 10 - 2;
int startX = (128 - totalW) / 2;
for (int i = 0; i < COIN_COUNT; i++) {
int x = startX + i * 10;
if (i == idx) display.fillCircle(x, dotY, 3);
else display.drawCircle(x, dotY, 3);
}
// Graph
drawGraph(idx);
display.display();
}
// Loading screen
void drawLoading(const char* msg) {
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 26, msg);
display.display();
}
// Setup
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
display.init();
display.flipScreenVertically();
display.setContrast(200);
drawLoading("Connecting WiFi...");
loadHistory();
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int tries = 0;
while (WiFi.status() != WL_CONNECTED && tries < 20) {
delay(500);
tries++;
}
if (WiFi.status() == WL_CONNECTED) {
drawLoading("Fetching prices...");
fetchPrices();
} else {
drawLoading("WiFi failed!");
delay(2000);
}
lastFetch = millis();
lastActivity = millis();
}
// Loop
void loop() {
unsigned long now = millis();
// Button debounce
bool btn = digitalRead(BUTTON_PIN);
if (btn == LOW && lastBtn == HIGH && (now - lastDebounce > 200)) {
currentCoin = (currentCoin + 1) % COIN_COUNT;
lastDebounce = now;
lastActivity = now;
}
lastBtn = btn;
// Sleep after inactivity
if (now - lastActivity >= SLEEP_MS) {
display.clear();
display.display(); // blank the OLED
display.displayOff(); // OLED power off
esp_sleep_enable_ext0_wakeup((gpio_num_t)BUTTON_PIN, 0); // wake on BOOT low
esp_deep_sleep_start(); // ESP32 enters deep sleep
}
// Periodic fetch
if (now - lastFetch >= FETCH_MS) {
fetchPrices();
lastFetch = now;
}
if (dataReady) drawCoin(currentCoin);
else drawLoading("Waiting...");
delay(100);
}
2 projects • 0 followers
High school student, who is interested in robotics, WRO competitions and ai development










Comments